Search code examples
iosswiftmultithreadingnsoperationqueueswift-protocols

How to use multithreaded operations for different ServiceProtocol - Swift


I am trying to use multithreaded operations with OperationQueue in my App.

Indeed, I would like to fetch all my data from all the different webservices during the launch, so that everything is ready to be use when I arrive on the Home page.

But I can't figure out how to properly do it.

Here is some code to visualize it :

class LauncherService {

    weak var serviceForecast: ForecastServiceProtocol?
    weak var serviceRadar: RadarServiceProtocol?
    weak var serviceWarning: WarningServiceProtocol?
    weak var serviceRain: RainServiceProtocol?

    init(serviceForecast: ForecastServiceProtocol = ForecastService.shared,
         serviceRadar: RadarServiceProtocol = RadarService.shared,
         serviceWarning: WarningServiceProtocol = WarningService.shared,
         serviceRain: RainServiceProtocol = RainService.shared) {

        self.serviceForecast = serviceForecast
        self.serviceRadar = serviceRadar
        self.serviceWarning = serviceWarning
        self.serviceRain = serviceRain
    }

    func requestAllServices(forCity city: City, completion: ((Result<Bool, ErrorResult>) -> Void)? = nil) {

        if let lat = city.lat,
            let lon = city.lon {
            self.fetchForecast(lat: lat, lon: lon)
            self.fetchRain(lat: lat, lon: lon)
        }

        self.fetchWarning()
        self.fetchRadar()
    }
}

I would like to wait until all the services have done their work before "notify" the HomePage that evertyhing is ready to go.

Here is the implementation of the methods from all the different ServiceProtocol :

I will show you only one, but the implementation is exactly the same for all the services :

func fetchForecast(lat: String, lon: String, completion: ((Result<Bool, ErrorResult>) -> Void)? = nil) {

        guard let service = serviceForecast else {
            completion?(Result.failure(ErrorResult.custom(string: "Missing service")))
            return
        }

        service.fetchForecast(lat: lat, lon: lon) { result in

            DispatchQueue.main.async {
                switch result {
                case .success(let forecast) :
                    // DO something with this Forecast Model Object
                    // Add it in an array on return in completion
                    // But I would like to regroup all the different Model Objects from all my services. 
                    completion?(Result.success(true))
                    break

                case .failure(let error) :
                    print("Something went wrong - \(error)")
                    completion?(Result.failure(error))
                    break
                }
            }
        }
    }

I have read that OperationQueue is the solution to adopt here for my use case.

Should I implement something like this ?

let queue = OperationQueue()

for service in services {
    queue.addOperation {
        self.fetch()
    }
}

queue.waitUntilAllOperationsAreFinished()

Solution

  • You can use DispatchGroup for that:

        let dispatchGroup = DispatchGroup()
    
        dispatchGroup.enter()
        fetchForecast {
            dispatchGroup.leave()
        }
    
        dispatchGroup.enter()
        fetchRain {
            dispatchGroup.leave()
        }
    
        dispatchGroup.notify(queue: .main) {
            print("All done")
        }
    

    The last block will execute when both of the request have finished loading.