1
votes

I need to wait until Alamofire request finishes getting data. (Error or value). I'm calling Alamofire function inside a for loop in another function so that Alamofire requests should be finished before second for loop called. For example; first loop -> first request -> second loop -> second request...so on. Now It goes first loop -> second loop -> and after all loops finishes requests response is turning.

Request Function:

 func like(sender_id: String, completion: @escaping (String?) -> ()){
    let group = DispatchGroup()
    if let key = api_key{
        let headers: HTTPHeaders = [
            "X-Auth-Token": key,
            "Accept": "application/json"
        ]
        group.enter()
        Alamofire.request(baseUrl + likeURL + sender_id, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).validate()
            .responseJSON { (response) -> Void in
                guard response.result.isSuccess else {
                    print("Error while doing : \(response.result.error)")
                    completion(nil)
                    group.leave()
                    return
                }
                if let error = response.error{
                    completion(nil)
                    group.leave()
                    print("Error Occured with Request: \(error)")
                }
                if let jsonData = response.data{
                    let json = JSON(data: jsonData)
                    print(json)
                    group.leave()
                    if let matched = json["match"].bool{
                        completion(matched.description)
                        print("Liked!: \(matched)")
                        if(matched){
                        }
                    }else{
                        group.leave()
                        "There is an error with JSON"
                    }
                }}
    }
}

Where I call:

 func like_all(completion: @escaping() -> ()){
    for user in SharedUsers.sharedUser.users{
        if let id = user.id{
            Network.sharedInstance.like(sender_id: id) { result in
                print(result)
            }
        }
        else{
            continue
        }
    }
    completion()
}
2
Unrelated, in your completion handler, you have a path of execution in which you call leave twice. You should fix that. Or, better, if you stay with this dispatch group pattern, I'd suggest using defer { group.leave() } at the start of the closure, and then pull out all the other leave calls, avoiding any risk of having a path of execution where you fail to call leave. Frankly, this is moot, as you shouldn't be using dispatch group at all, but just FYI. - Rob
I looked promisekit but I'm not sure If It is the right way for my problem. - Mr Some Dev.
Below I outlined two alternative approaches for how to do this, but PromiseKit is definitely yet another way to solve this. The key idea is that you never want to wait for some asynchronous process before starting another, but use one of these patterns to asynchronously trigger one request when the prior one finishes. This avoids blocking a thread (and eliminates any deadlock risks). - Rob

2 Answers

1
votes

You are using dispatch group, obviously with the intent on waiting for the group at the end of the function. But you're not waiting for it, so you're not getting the synchronous behavior you were looking for.

But that's good, because if you wait for that group (or semaphore, the other pattern to achieve this behavior) on the main thread, not only will you be blocking the main thread (which results in horrible UX and risk having your app killed by watchdog process), you're going to deadlock because responseJSON uses the main queue for it's completion handler. So before you add the wait() call on your dispatch group/semaphore, make sure you dispatch this whole for loop asynchronously to some background thread. That avoids blocking the main thread and eliminates the deadlock risk.

But this whole pattern is fundamentally flawed, as you really shouldn't use dispatch groups or semaphores to make it synchronous at all. That raises a few questions:

The first question is why you want to make this synchronous. Network requests have inherent latency, so you performing a sequence of them will be very slow. Only do this sequence of requests if you absolutely have to (e.g. each request cannot be formed because it needs something from the response of the prior request). But that doesn't appear to be the case here. So why make this process unnecessary so.

But let's assume for a second that you absolutely have to perform these sequentially (not true here, from what I can tell, but let's tease this out). Then there are two patterns for performing a series of requests consecutively rather than concurrently:

  • You can either lose this for loop entirely, and simply have a routine that sends the n-th request and sends request n+1 in its completion handler. That completely eliminates the need for dispatch groups/semaphores to block a thread.

  • Or you can wrap this in operation (e.g. https://stackoverflow.com/a/27022598/1271826) and use operation queue.

0
votes

I solve it by calling like function with index + 1 everytime Alamofire returns value. I also call function in completion part of fetch request.

Here is the code:

    @objc func action(_ sender: LGButton) {
        sender.titleString = "Started to Like :)"
        Network.sharedInstance.get_rec(completion: { (result) in
            if(result != nil)
            {
                Network.sharedInstance.like(sender: 0, completion: { (result) in
                    //print(result)
                })
            }
        })
}

Like Function:

 func like(sender: Int, completion: @escaping (String?) -> ()){
    if let key = api_key{
        let headers: HTTPHeaders = [
            "X-Auth-Token": key,
            "Accept": "application/json"
        ]
        print(sender)
        print(SharedUsers.sharedUser.users.count)
        if(sender < SharedUsers.sharedUser.users.count){
            if let user_id = SharedUsers.sharedUser.users[sender].id{
                Alamofire.request(baseUrl + likeURL + user_id, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).validate()
                    .responseJSON { (response) -> Void in
                        guard response.result.isSuccess else {
                            print("Error while doing : \(response.result.error)")
                            completion(nil)
                            return
                        }
                        if let error = response.error{
                            completion(nil)
                            print("Error Occured with Request: \(error)")
                        }
                        if let jsonData = response.data{
                            let json = JSON(data: jsonData)
                            if let matched = json["match"].bool{
                                completion(matched.description)
                                print("Liked!: \(matched)")
                                if(sender <= SharedUsers.sharedUser.users.count){
                                    self.like(sender: sender + 1, completion: {result in
                                        //print(result)
                                    })
                                }else{
                                    return
                                }
                                if(matched){
                                }
                            }else{
                                "There is an error with JSON"
                            }}}
            }else{return}
        }}}