18

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"
Riajur Rahman
  • 1,976
  • 19
  • 28
King
  • 259
  • 1
  • 4
  • 12

3 Answers3

45

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
    }
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • what about if call failed ? can I use dispatchGroup.leave() in error block also? – King May 28 '18 at 19:44
  • 2
    Yes. You must always call `leave()` exactly once for every `enter()`, whether you succeed or fail. – Rob Napier May 28 '18 at 21:01
  • @RobNapier Last time I have used dispatchGroup I couldn't cancel request. It is still true, right? – k-thorat May 29 '18 at 04:43
  • 1
    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. – Rob Napier May 29 '18 at 12:20
  • Got it. Thanks a lot @RobNapier – k-thorat May 29 '18 at 12:38
  • Here, the response from second request is getting only after that from first request. But I need second response first. – Rashid KC Feb 08 '20 at 06:25
  • 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. – Farras Doko Feb 25 '21 at 12:19
  • @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 ? – Jack Mar 03 '21 at 09:07
  • @RobNapier on failure condition in API's then set DispatchQueue.main.async {dispatchGroup.leave()}, does it works too? – kiran Dec 29 '22 at 20:38
  • Every `enter` must be balanced with exactly one `leave`, whether success or failure. – Rob Napier Dec 29 '22 at 20:42
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
k-thorat
  • 4,873
  • 1
  • 27
  • 36
  • thanks a lot kthorat for the quick reply.. but I wanted to know how will I triggered this cancelOperation() here ? – King May 28 '18 at 17:13
  • 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? – k-thorat May 29 '18 at 04:42
  • @k-thorat What if I want to call two or more APIs in order? – Saurabh Sep 01 '20 at 11:43
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.
    }
}
Naresh
  • 869
  • 8
  • 17
  • 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. – vadian Aug 01 '23 at 06:57