How to do two concurrent API calls in swift 4
Asked Answered
K

3

19

Thanks in advance for help, I have two API calls, both are concurrent and any call could be success first(I don't want call in sequence), after success of both calls, I have to stop my activity indicator and reload my tableView, Here is my code but I don't know is this the right way or not and how to reload my tableView and stop my activity indicator.

func downloadDetails(){
    let operationQueue: OperationQueue = OperationQueue()
    let operation1 = BlockOperation() {
    WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in

          if let success = data {
              DispatchQueue.main.async {
                  (success code)
              }
           }
        })

        let operation2 = BlockOperation() {
        webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in

                if let success = data {
                    DispatchQueue.main.async {
                       (success code)
                    }
                }
            })
        }
        operationQueue.addOperation(operation2)
    }
    operationQueue.addOperation(operation1)
}
downloadDetails() "calling function"
Klaraklarika answered 27/5, 2018 at 23:32 Comment(0)
K
48

This is exactly the use case for DispatchGroup. Enter the group for each call, leave the group when the call finishes, and add a notification handler to fire when they're all done. There's no need for a separate operation queue; these are already asynchronous operations.

func downloadDetails(){
    let dispatchGroup = DispatchGroup()

    dispatchGroup.enter()   // <<---
    WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in

        if let success = data {

            DispatchQueue.main.async {
                (success code)
                dispatchGroup.leave()   // <<----
            }
        }
    })

    dispatchGroup.enter()   // <<---
    webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in

        if let success = data {

            DispatchQueue.main.async {
               (success code)
               dispatchGroup.leave()   // <<----
            }
        }
    })

    dispatchGroup.notify(queue: .main) {
        // whatever you want to do when both are done
    }
}
Klaxon answered 28/5, 2018 at 0:26 Comment(11)
what about if call failed ? can I use dispatchGroup.leave() in error block also?Klaraklarika
Yes. You must always call leave() exactly once for every enter(), whether you succeed or fail.Klaxon
@RobNapier Last time I have used dispatchGroup I couldn't cancel request. It is still true, right?Nineteenth
In the way that you mean it, the answer is yes, but it's not a good way to think about it. You must leave exactly once for every time you enter. You can absolutely cancel the operation; but you still need to leave the group. (I know this is pedantic; but it's important to think about groups in terms of what they really do.) If you mean you can't avoid running the notify block, that is true. There's no way to unsubscribe from the group completion. If you want that kind of cancelation, you can move up a layer to Operations, but I have find them too complicated for most problems.Klaxon
Got it. Thanks a lot @RobNapierNineteenth
Here, the response from second request is getting only after that from first request. But I need second response first.Behest
Hi @RobNapier , I want to pull 4 APIs together, I did that DispatchGroup enter() and leave() but nothing changed. The APIs waiting for another one to complete and it's not concurrent.Vladimir
@RobNapier if i use dispatchGroup.notify(queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.default)) & DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { self.dispatchGroup.leave() // <<---- } is it fine ?Danged
@RobNapier on failure condition in API's then set DispatchQueue.main.async {dispatchGroup.leave()}, does it works too?Sacrificial
Every enter must be balanced with exactly one leave, whether success or failure.Klaxon
FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={)Salivate
N
1

I would use OperationQueue.

It is preferred for long running task and give you control to cancel request, if needed.

At the end of each operation you can check operation count to know remaining operations.

I have added pseudo code.

let operationQueue: OperationQueue = OperationQueue()

func downloadDetails(){

    let operation1 = BlockOperation() { [weak self] in

        guard let strongSelf = self else {
            return
        }

        sleep(2)

        DispatchQueue.main.async {
            strongSelf.handleResponse()
        }

        let operation2 = BlockOperation() { [weak self] in

            guard let strongSelf = self else {
                return
            }

            sleep(2)

            DispatchQueue.main.async {
                strongSelf.handleResponse()
            }
        }
        strongSelf.operationQueue.addOperation(operation2)
    }

    self.operationQueue.addOperation(operation1)
}

func handleResponse() {
    print("OPERATIONS IN PROGRESS: \(self.operationQueue.operations.count)")
    if self.operationQueue.operations.count == 0 {
        print("ALL OPERATIONS ARE COMPLETE")
    }
}

func cancelOperation() {
    self.operationQueue.cancelAllOperations()
}

This prints

OPERATIONS IN PROGRESS: 1
OPERATIONS IN PROGRESS: 0
ALL OPERATIONS ARE COMPLETE
Nineteenth answered 28/5, 2018 at 0:59 Comment(3)
thanks a lot kthorat for the quick reply.. but I wanted to know how will I triggered this cancelOperation() here ?Klaraklarika
you should cancel request if you don't need it anymore. Example, if you start request on screen you should cancel after user leaves screen. So ViewWillAppear is making request then you can cancel on ViewWillDisappear. makes sense?Nineteenth
@Nineteenth What if I want to call two or more APIs in order?Fir
M
0

Dispatch group may fail in one case, suppose the first api response returns before entering into the second group. So in that case your notify block will get called without second api. So for preventing that issue you have to add all the enters statement before any api call. Like -

func download(){
    let dispatchGroup = DispatchGroup()
    /// Enter into the group for all the apis from here only.
    dispatchGroup.enter()  
    dispatchGroup.enter()   
    
    ApiManager.shared.data(request: firstRequest, withCompletion: {(data: Any? , error: Error?) -> Void in
        dispatchGroup.leave()  
    })

    ApiManager.shared.data(request: secondRequest, withCompletion: {(data: Any? , error: Error?) -> Void in
        dispatchGroup.leave() 
    })

    dispatchGroup.notify(queue: .main) {
        /// From here you may notify to hide indicator and reload the table.
    }
}
Mutation answered 1/8, 2023 at 6:47 Comment(1)
This is not an issue. Even if the second enter is placed right before the second API call it's executed before the completion handler of the first API call is executed.Kalmar

© 2022 - 2025 — McMap. All rights reserved.