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

时间:2016-03-11 09:25:13

标签: ios swift alamofire rx-swift

我仍然是Reactive编程的初学者,而且RxSwift一般。 我想连锁两个不同的操作。在我的情况下,我只想从Web服务器下载一个zip文件,然后在本地解压缩。 我也希望,同时显示下载文件的进度。 所以我开始创建第一个observable:

myapp.example/**

之后,我为解压缩

创建了一个类似的函数
 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)

}

现在我在“查看模型层”上有这个逻辑,所以我下载 - &gt;订阅完成 - &gt;解压

我想要的是将两个Observable合二为一,以便先执行下载,然后再完成解压缩文件。有没有办法做到这一点?

2 个答案:

答案 0 :(得分:4)

Concat运算符需要相同的数据类型

实际上,concat运算符允许您强制执行可观察的序列,但使用concat时可能遇到的问题是concat运算符要求Observable } s具有相同的泛型类型。

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运算符允许您链接Observable s

序列

这是一个例子。

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)
    })

注意getRequestReadHTML类型Observable<String>的类型getTagsFromHtml,而函数Observable<Tag>的类型为flatMap

使用多个flatMaps可以提高发射频率

要小心,因为1...n运算符接收数组(例如[1,2,3])或序列(例如Observable)并将所有元素作为发射发出。这就是为什么产生1...1的转换。

如果您定义了一个可观察的网络调用,并且您确定只有一个发射,那么您将不会遇到任何问题,因为它的转换是flatMap(即一个Observable到一个NSData)。太好了!

但是,如果您的Observable有多个排放,请非常小心,因为链式1...n运营商将意味着排放将呈指数增长(?)。

一个具体的例子是当第一个观察者发射3个发射时,flatMap算子变换1...n,其中n = 2,这意味着现在共有6个发射。另一个flatMap运算符可以再次转换ts,其中n = 2,这意味着现在总共有12个发射。仔细检查这是否是您的预期行为。

答案 1 :(得分:1)

您可以使用concat运算符链接这两个Observable。生成的Observable将从第一个发送next个值,并在完成时从第二个发送rx_download个值。

有一点需要注意:您将从rx_unzip获得从0.0到1.0的进度值,然后map的进度将从0.0开始。如果要在单个进度视图上显示进度,这可能会使用户感到困惑。

可能的方法是显示描述正在发生的事情的标签以及进度视图。您可以concat每个Observable到包含进度值和描述文本的元组,然后使用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 }) 。它看起来像这样:

Mat

当然,有许多可能的解决方案,但这更像是一个设计问题而不是编码问题。