简体   繁体   English

RxSwift,我如何链接不同的可观察量

[英]RxSwift, how do I chain different observables

I am still a beginner in Reactive programming, and RxSwift in general. 我仍然是Reactive编程的初学者,以及RxSwift。 I want to chain two different operation. 我想连锁两个不同的操作。 In my case I simply want to download a zip file from a web server, and then unzip it locally. 在我的情况下,我只想从Web服务器下载一个zip文件,然后在本地解压缩。 I also want, at the same time to show the progress of the downloaded files. 我也希望,同时显示下载文件的进度。 So I started creating the first observable: 所以我开始创建第一个observable:

 class func rx_download(req:URLRequestConvertible, testId:String) -> Observable<Float> {

    let destination:Request.DownloadFileDestination = ...

    let obs:Observable<Float> = Observable.create { observer in
       let request =  Alamofire.download(req, destination: destination)
        request.progress { _, totalBytesWritten, totalBytesExpectedToWrite in
            if totalBytesExpectedToWrite > 0 {
                observer.onNext(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
            }
            else {
                observer.onNext(0)
            }
        }
        request.response {  _, response, _, error in
            if let responseURL = response {
                if responseURL.statusCode == 200 {
                    observer.onNext(1.0)
                    observer.onCompleted()
                } else  {
                    let error = NSError(domain: "error", code: responseURL.statusCode, userInfo: nil)
                    observer.onError(error)
                }
            } else {
                let error = NSError(domain: "error", code: 500, userInfo: nil)
                observer.onError(error)
            }

            }
            return AnonymousDisposable () {
                request.cancel()
            }
        }
    return obs.retry(3)

}

After that, I create a similar function for the unzip 之后,我为解压缩创建了一个类似的功能

class func rx_unzip(testId:String) -> Observable<Float> {
    return Observable.create { observer in
        do {
            try Zip.unzipFile(NSURL.archivePath(testId), destination: NSURL.resourceDirectory(testId), overwrite: true, password: nil)
                {progress in
                    observer.onNext(Float(progress))
                }
        } catch let error {
            observer.onError(error)
        }
        observer.onCompleted()
        return NopDisposable.instance
    }
}

For now I have this logic on the "View model layer", so I download-> subscribe on completed-> unzip 现在我在“查看模型层”上有这个逻辑,所以我下载 - >订阅完成 - >解压缩

What I want is to combine the two Observable in one, in order to perform the download first, then on completed unzip the file. 我想要的是将两个Observable合二为一,以便先执行下载,然后再完成解压缩文件。 Is there any way to do this? 有没有办法做到这一点?

Concat operator requires the same data type Concat运算符需要相同的数据类型

Indeed, the concat operator allows you to enforce the sequence of observables, however an issue you might encounter with using concat is that the concat operator requires that the Observable s have the same generic type. 实际上, concat运算符允许您强制执行可观察的序列,但是使用concat可能遇到的问题是concat运算符要求Observable具有相同的泛型类型。

let numbers     : Observable<Int>    = Observable.from([1,2,3])
let moreNumbers : Observable<Int>    = Observable.from([4,5,6])
let names       : Observable<String> = Observable.from(["Jose Rizal", "Leonor Rivera"])


// This works
numbers.concat(moreNumbers)

// Compile error
numbers.concat(names)

FlatMap operator allows you to chain a sequence of Observable s FlatMap运算符允许您链接一系列Observable

Here's an example. 这是一个例子。

class Tag {
    var tag: String = ""
    init (tag: String) {
        self.tag = tag
    }
}

let getRequestReadHTML : Observable<String> = Observable
                            .just("<HTML><BODY>Hello world</BODY></HTML>")

func getTagsFromHtml(htmlBody: String) -> Observable<Tag> {
    return Observable.create { obx in

        // do parsing on htmlBody as necessary

        obx.onNext(Tag(tag: "<HTML>"))
        obx.onNext(Tag(tag: "<BODY>"))
        obx.onNext(Tag(tag: "</BODY>"))
        obx.onNext(Tag(tag: "</HTML>"))

        obx.onCompleted()

        return Disposables.create()
    }
}

getRequestReadHTML
    .flatMap{ getTagsFromHtml(htmlBody: $0) }
    .subscribe (onNext: { e in
        print(e.tag)
    })

Notice how getRequestReadHTML is of type Observable<String> while the function getTagsFromHtml is of type Observable<Tag> . 请注意getRequestReadHTML的类型是Observable<String>而函数getTagsFromHtml的类型是Observable<Tag>

Using multiple flatMaps can increase emission frequency 使用多个flatMaps可以增加发射频率

Be wary however, because the flatMap operator takes in an array (eg [1,2,3]) or a sequence (eg an Observable) and will emit all of the elements as an emission. 但要小心,因为flatMap运算符接受一个数组(例如[1,2,3])或一个序列(例如一个Observable)并将所有元素作为发射发出。 This is why it is known to produce a transformation of 1...n . 这就是为什么产生1...n的变换的原因。

If you defined an observable such as a network call and you are sure that there will only be one emission, you will not encounter any problems since its transformation is a 1...1 (ie one Observable to one NSData). 如果您定义了一个可观察的网络调用,并且您确定只有一个发射,那么您将不会遇到任何问题,因为它的转换是1...1 (即一个Observable到一个NSData)。 Great! 大!

However, if your Observable has multiple emissions, be very careful because chained flatMap operators will mean emissions will exponentially(?) increase. 但是,如果您的Observable有多个排放,请非常小心,因为链式flatMap运算符将意味着排放将呈指数增长(?)。

A concrete example would be when the first observable emits 3 emissions, the flatMap operator transforms 1...n where n = 2, which means there are now a total of 6 emissions. 一个具体的例子是当第一个可观测量发射3个发射时,flatMap运算符变换1...n ,其中n = 2,这意味着现在总共有6个发射。 Another flatMap operator could again transform 1...n where n = 2, which means there are now a total of 12 emissions. 另一个flatMap运算符可以再次转换1...n ,其中n = 2,这意味着现在总共有12个发射。 Double check if this is your expected behavior. 仔细检查这是否是您的预期行为。

You can use concat operator to chain these two Observables. 您可以使用concat运算符链接这两个Observable。 The resulting Observable will send next values from the first one, and when it completes, from the second one. 生成的Observable将从第一个值发送next值,并在完成时从第二个值发送。

There is a caveat: you will get progress values ranging from 0.0 to 1.0 from rx_download and then again the progress from rx_unzip will start with 0.0. 有一个警告:你会得到进度值范围从0.0到1.0,从rx_download ,然后再从进度rx_unzip将0.0开始。 This might be confusing to the user if you want to show the progress on a single progress view. 如果要在单个进度视图上显示进度,这可能会使用户感到困惑。

A possible approach would be to show a label describing what is happening along with the progress view. 一种可能的方法是显示描述正在发生的事情的标签以及进度视图。 You can map each Observable to a tuple containing the progress value and the description text and then use concat . 您可以map每个Observable map到包含进度值和描述文本的元组,然后使用concat It can look like that: 它看起来像这样:

let mappedDownload = rx_download.map {
    return ("Downloading", $0)
}

let mappedUnzip = rx_download.map {
    return ("Unzipping", $0)
}

mapped1.concat(mapped2)
 .subscribeNext({ (description, progress) in
    //set progress and show description
})

Of course, there are many possible solutions, but this is more of a design problem than a coding one. 当然,有许多可能的解决方案,但这更像是一个设计问题而不是编码问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM