Matthew Gannon
Matthew Gannon

Reputation: 11

Is there a way in swift to make sure an API call only gets called once when in a race condition

In my app, I need to call a getGroup() function that reaches out to our CMS using user information, and places them in a group depending on that info and content currently in the CMS. This group info is included in the URL of our other API calls.

The problem is that when the app starts and the user's group isn't cached yet, each of these API calls trigger getGroup to actually make the API call instead of just getting the cached group. I'd like to reduce it so the call is only made once, and the other calls to the function wait until a response is heard.

Pseudocode example of what I would like to do:

var isGettingGroup = false
func getGroup(completion: (group?, error?)) {

    if isGettingGroup {
        wait for notification
    }
    if let group = groupFromCache() {
        completion(group, nil)
    } else {
        isGettingGroup = true
        callGetGroupAPI() { group, error in
            completion(group, error)
            cacheGroup(group)
            isGettingGroup = false
            send notification to continue
        }
    }
}

I've tried using a semaphore, but I think I need something a bit more global, like a post from NotificationCenter. My main problem is pausing the individual function call depending on a notification instead of waiting for an allotted amount of time. I've used DispatchGroups many times, but this seems like the opposite problem - multiple functions waiting on one call, instead of on function/block waiting on multiple.

Thanks in advance

Upvotes: 0

Views: 1084

Answers (1)

matt
matt

Reputation: 535890

the call is only made once, and the other calls to the function wait until a response is heard

The function should run its code on a serial background queue synchronously. This makes it impossible for it to be called simultaneously by two different threads. Here's a pseudocode of your pseudocode; untested, but it shows something I believe will work:

let q = DispatchQueue()
func getGroup(completion: (group?, error?)) {
    q.sync { // lock out any other calls to getGroup until we finish
        let g = DispatchGroup()
        g.enter()
        if let group = groupFromCache() {
            completion(group, nil)
            g.leave()
        } else {
            callGetGroupAPI() { group, error in
                completion(group, error)
                cacheGroup(group)
                g.leave()
            }
        }
        g.notify()
    }
}

I'm not entirely sure the dispatch group is needed but I've put it in to keep everything within the bounds of the sync.

EDIT The OP says that the dispatch group is in fact needed, and you need to say g.wait() instead of notify(), but otherwise this works.

Upvotes: 1

Related Questions