10

我仍然是 Reactive 编程和 RxSwift 的初学者。我想链接两个不同的操作。就我而言,我只是想从 Web 服务器下载一个 zip 文件,然后在本地解压缩。我也想,同时显示下载文件的进度。所以我开始创建第一个 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)

}

之后,我为解压缩创建了一个类似的函数

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

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

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

4

2 回答 2

7

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

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

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 运算符允许您链接一系列Observables

这是一个例子。

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

注意 how getRequestReadHTMLis 是 typeObservable<String>而 functiongetTagsFromHtml是 type Observable<Tag>

使用多个 flatMap 可以增加发射频率

但是要小心,因为flatMap操作符接受一个数组(例如 [1,2,3])或一个序列(例如一个 Observable)并将所有元素作为发射发射。这就是为什么已知它会产生 的转换1...n

如果你定义了一个 observable,比如一个网络调用,并且你确定只会有一个发射,你不会遇到任何问题,因为它的转换是一个1...1(即一个 Observable 到一个 NSData)。伟大的!

但是,如果您的 Observable 有多个排放量,请非常小心,因为链式flatMap运算符将意味着排放量将呈指数级(?)增加。

一个具体的例子是,当第一个 observable 发出 3 个排放时,flatMap 运算符转换1...nn = 2,这意味着现在总共有 6 个排放。另一个 flatMap 运算符可以再次转换1...nn = 2 的位置,这意味着现在总共有 12 个排放量。仔细检查这是否是您的预期行为。

于 2016-11-15T15:43:43.207 回答
1

您可以使用concatoperator 链接这两个 Observable。结果 Observable 将从next第一个发送值,并在完成时从第二个发送值。

有一个警告:您将获得从 0.0 到 1.0 的进度值,rx_download然后再一次,进度将从rx_unzip0.0 开始。如果您想在单个进度视图上显示进度,这可能会让用户感到困惑。

一种可能的方法是显示一个描述正在发生的事情的标签以及进度视图。您可以将map每个 Observable 包含到一个包含进度值和描述文本的元组中,然后使用concat. 它看起来像这样:

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

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

于 2016-03-11T11:06:09.087 回答