[英]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)
Observable
s 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>
。
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.