How to synchronize data between threads (context) in iOS?
sobes.tech AI
Answer from AI
For synchronization, the following mechanisms can be used:
-
NSLock: A primitive blocking mutex.
let lock = NSLock() func doSomething() { lock.lock() // Critical section of code lock.unlock() } -
NSRecursiveLock: Allows a thread to acquire the lock multiple times without deadlock.
let recursiveLock = NSRecursiveLock() func recursiveFunction(level: Int) { recursiveLock.lock() if level < 3 { recursiveFunction(level: level + 1) } recursiveLock.unlock() } -
NSCondition: Allows threads to wait for a certain condition before continuing.
let condition = NSCondition() var dataAvailable = false func producer() { condition.lock() // Data production dataAvailable = true condition.signal() // Signal waiting threads condition.unlock() } func consumer() { condition.lock() while !dataAvailable { condition.wait() // Wait until condition is met } // Data processing dataAvailable = false condition.unlock() } -
NSConditionLock: A mutex that can only be acquired when a certain condition-value is met.
let conditionLock = NSConditionLock(condition: 0) let DATA_READY = 1 func producer() { conditionLock.lock(when: 0) // Acquire when condition is 0 // Data production conditionLock.unlock(withCondition: DATA_READY) // Release lock and set condition to DATA_READY } func consumer() { conditionLock.lock(when: DATA_READY) // Acquire when condition is DATA_READY // Data processing conditionLock.unlock(withCondition: 0) // Release lock and set condition to 0 } -
Dispatch Queues (GCD): Using serial dispatch queues for access to shared resources.
let serialQueue = DispatchQueue(label: "com.yourapp.serialQueue") var sharedResource = 0 func updateResource() { serialQueue.async { // Access to sharedResource only from this queue sharedResource += 1 } } -
NSOperationQueue: Allows controlling dependencies and priorities of operations.
let operationQueue = OperationQueue() var sharedOperationResource = 0 let operation1 = BlockOperation { // Access to sharedOperationResource sharedOperationResource += 1 } operationQueue.addOperation(operation1) -
Actors (Swift 5.5+): Provide state isolation, preventing concurrent access from different contexts.
actor Counter { private var count = 0 func increment() { count += 1 } func getCount() -> Int { return count } } let counter = Counter() Task { await counter.increment() let currentCount = await counter.getCount() print(currentCount) } -
Core Data Contexts: For synchronizing data contexts, hierarchical contexts (parent-child) or mergeChanges(fromContextDidSaveNotification:) can be used.
// Example with hierarchy (parent - background, child - UI) let privateMoc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateMoc.persistentStoreCoordinator = persistentStoreCoordinator let mainMoc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) mainMoc.parent = privateMoc // Example with mergeChanges NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: nil) @objc func contextDidSave(_ notification: Notification) { guard let savingContext = notification.object as? NSManagedObjectContext else { return } if savingContext != mainMoc { // If save occurred not in main context mainMoc.perform { mainMoc.mergeChanges(fromContextDidSave: notification) } } } -
Atomic operations: Using atomic primitives (e.g., from
os/lock.h) for simple operations.
The choice of method depends on the complexity of the task, data type, and performance requirements. Actors and GCD are modern preferred approaches.