May 23, 2020

Multiple simultaneous network requests

Today we will learn how to "combine" multiple publishers to create a new one. This has many use cases for example it can be used to download multiple images in parallel combining an existing Publisher which downloads an image from internet .

The sample App we will use to understand it, is a GridView of images where we want to download all images simultaneously before rendering


GridView

Let's set the stage first, we already have a method called fetchImage using URLSession's dataTaskPublisher to download an image from Internet. I am using this awesome site called Picsum Photos to download a random picture from web of given size (in our example it is 80x80 points).


private func fetchImage() -> AnyPublisher<Image, Never> {
        let url = URL(string: "https://picsum.photos/80")!
        return URLSession.shared.dataTaskPublisher(for: url)
            .map {
                guard let uiImage = UIImage(data: $0.data) else {
                    return Image.default
                }
                return Image(uiImage: uiImage)
        }
        .replaceError(with: Image.default)
        .eraseToAnyPublisher()
    }
extension Image {
    static var `default` = Image(systemName: "photo")
}

Given this method, let's see how we can leverage this to achieve downloading multiple images in parallel. So without any delay let's see the code. All steps in the code are commented to explain what is going on.


// Download n images
func fetch(n: Int) {
    //Make an array of publishers 
    let arrayOfPublishers = (0...n).map { _ in
        fetchImage()
    }
    //Create a publisher of a sequence
    Publishers.Sequence(sequence: arrayOfPublishers)
        .flatMap{ $0 } //merges the output from all returned publishers into a single stream of Image.
  										//AnyPublisher<Image, Never> to Image
        .collect() //collects all received items and returns them as an array upon completion.
                   //this is what collects all images and finishes when all are available 
        .receive(on: RunLoop.main) // receive on main thread so that we can update our UI with images
        .sink { //create a subscription, here the value we will receive is going to be [Image] 
            //we have all images $0, downloaded now 
        }
        .store(in: &cancellableSet) //so that we retain subscription
}

The full code is available in this Github gist


If you have any questions, doubts or feedback get in touch and I would be happy to help.


Share this article
Tagged with: