Search code examples
swiftrx-swiftreactive

How to combine multi Observable in RxSwift


Let's say I create an Observable for each image download

for _ in 0...N {
    let s : Single<UIImage?> = fetchImage(from url)
}

How can I:

  • create a new Observable to emit an event when a download is completed
  • create a new Observable to emit only completed event when all download is completed

Thanks!


Solution

  • create a new Observable to emit an event when a download is completed

    Look at the method imageObservable for a possible implementation.

    create a new Observable to emit only completed event when all download is completed

    The zip (documentation) operator might be what you are looking for.

    import RxSwift
    import UIKit
    
    enum CustomError: Error {
        case someError
    }
    
    class Executer {
    
        let disposeBag = DisposeBag()
    
        func execute() {
            let imageURLs = [
                URL(string: "http://via.placeholder.com/350x150")!, 
                URL(string: "http://via.placeholder.com/350x150")!
            ]
            let imageObservables = imageURLs.map { self.imageObservable(url: $0) }
            Observable
                .zip(imageObservables) // wait for all image requests to finish
                .subscribe(onNext: { images in
                    // here you have every single image in the 'images' array
                    images.forEach { print($0) }
                })
                .disposed(by: disposeBag)
         }
    
         // wrap 'URLSession' datatask into an observable
         func imageObservable(url: URL) -> Observable<UIImage> {
            return Observable.create { observer in
                URLSession
                    .shared
                    .dataTask(with: url, completionHandler: { (data, response, error) -> Void in
                        if let error = error {
                            observer.onError(error)
                            return
                        }
                        guard let data = data, let image = UIImage(data: data) else {
                            observer.onError(CustomError.someError)
                            return
                        }
                        observer.onNext(image)
                        observer.onCompleted()
                    })
                    .resume()
                return Disposables.create()
            }
        }
    }
    

    This answer might also be relevant for you.