iosswiftgrand-central-dispatchdispatchgroupdispatchsemaphore

How to make a common resource thread safe when using dispatch group?


I have a class User which needs to be updated every time a user opens the app

class User : NSObject, NSCoding {
    var vehicles : [Vehicles]
    var bankaccounts : [BankAccounts]
    var friends : [Friends]
}

In my home screen ViewController, I have a function that gets the data from backend using 3 Alamofire requests. Finally, I save the data in UserDefaults. DispatchGroup was the first thing that came to my mind to implement this. Here is the code

func loadUserData {
    var user = User()

    let userDataDispatchGroup = DispatchGroup()

    userDataDispatchGroup.enter()
    AF.request(...).responseJSON {
        //update the user.vehicles array
        userDataDispatchGroup.leave()
    }
    
    userDataDispatchGroup.enter()
    AF.request(...).responseJSON {
        //update the user.bankaccounts array
        userDataDispatchGroup.leave()
    }

    userDataDispatchGroup.enter()
    AF.request(...).responseJSON {
        //update the user.friends array
        userDataDispatchGroup.leave()
    }

    userDataDispatchGroup.notify(queue: .main) {
        let encodedData  = NSKeyedArchiver.archivedData(withRootObject: user)
        UserDefaults.standard.set(encodedData, forKey: "user")
    }

}

But I am not clear about the thread-safety of my user object. Since it will be updated in three different callbacks, would thread safety be an issue here? If yes, what would be the best way to solve the issue? I was thinking of using DispatchSemaphore. But I am not sure if that's the correct approach.


Solution

  • You asked:

    But I am not clear about the thread-safety of my user object. Since it will be updated in three different callbacks, would thread safety be an issue here?

    There are no thread-safety issues in your code snippet because Alamofire calls its completion handlers on the main thread. They do that to help mitigate multithreading concerns. There is no need for any DispatchQueue.main.async in this case. As the Alamofire documentation says:

    Closures passed to response handlers are executed on the .main queue by default, but a specific DispatchQueue can be passed on which to execute the closure.

    So unless you did something unusual (such as overriding the default .main queue with some concurrent DispatchQueue), Alamofire will run its completion handlers on the main thread, mitigating the thread-safety concerns.

    If you were using a different API that didn’t call its completion handlers on the main thread (e.g. URLSession.shared calls its completion handlers on a background queue), then there might be concerns, but not with Alamofire. (And even URLSession uses a serial background queue, so there wouldn’t be issues using your pattern, where you are updating a local variable.)

    Bottom line, as long as you’re not mutating/accessing a variable from multiple threads at the same time, threads-safety concerns are largely mitigated.