RxSwift, how can I bind various observables

I'm still new to reactive programming and RxSwift in general. I want to link two different operations. In my case, I just want to download the zip file from the web server, and then unzip it locally. I also want to at the same time show the progress of the downloaded files. So I started creating the first observables:

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

At the moment, I have this logic on โ€œView Model Layerโ€, so I load-> subscribe to completed-> unzip

I want to combine two Observable in one in order to download first and then finish unpacking the file. Is there any way to do this?

+8
ios swift alamofire rx-swift
source share
2 answers
Operator

Concat requires the same data type

Indeed, the Concat operator allows you to execute a sequence of observables, but the problem you may encounter when using Concat is that the Concat operator requires that the Observable have the same general type.

 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) 

The FlatMap operator allows you to group the sequence of Observable s

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

Note that getRequestReadHTML is of type Observable<String> , and the getTagsFromHtml function is of type Observable<Tag> .

Using multiple flat cards may increase the frequency of radiation

Be careful, as the flatMap operator accepts an array (for example, [1,2,3]) or a sequence (for example, observable) and will emit all elements as radiation. That is why it is known that the transformation 1...n was obtained.

If you define an observable, such as a network call, and you are sure that there will be only one radiation, you will not encounter any problems, since its conversion is 1...1 (i.e. one observation of one NSData ) Fine!

However, if your Observable has multiple outliers, be very careful because the chained flatMap operators will mean that outliers will increase exponentially (?).

A concrete example is when the first observable emits 3 emissions, the flatMap operator converts 1...n , where n = 2, which means that there are currently only 6 outliers. Another flatMap operator could again convert 1...n , where n = 2, which means there are currently only 12 outliers. Double check if this is your expected behavior.

+2
source share

You can use the concat operator to combine these two observed objects. The resulting Observable will send the next values โ€‹โ€‹from the first and, when it completes, from the second.

There is a caution: you will get progress values โ€‹โ€‹from 0.0 to 1.0 from rx_download , and then again progress from rx_unzip will start at 0.0. This can confuse the user if you want to show progress on a single view of progress.

A possible approach would be to show a label describing what happens with the idea of โ€‹โ€‹the progress. You can map each Observed to a tuple containing the progress value and description text, and then use concat . It might look like this:

 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 a design problem than coding.

+1
source share

All Articles