Sobes.tech
Middle+

How to synchronize data between threads and ensure context consistency?

sobes.tech AI

Answer from AI

The following approaches are used for data synchronization and ensuring context consistency in a multithreaded iOS application:

  1. NSLock / Lock: Simple mutexes for locking critical sections of code.

    import Foundation
    
    class ThreadSafeCounter {
        private var count: Int = 0
        private let lock = NSLock()
    
        func increment() {
            lock.lock()
            defer { lock.unlock() } // Guaranteed lock release
            count += 1
        }
    
        func getCount() -> Int {
            lock.lock()
            defer { lock.unlock() }
            return count
        }
    }
    
  2. NSRecursiveLock: Allows re-locking from the same thread.

    import Foundation
    
    class RecursiveLocker {
        private let lock = NSRecursiveLock()
    
        func firstMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("First method")
            secondMethod() // Re-locking is possible
        }
    
        func secondMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Second method")
        }
    }
    
  3. NSCondition / NSConditionLock: Allows threads to wait for a specific condition or value before continuing execution. Used for implementing producer-consumer pattern.

    import Foundation
    
    class Buffer {
        private var items: [Int] = []
        private let condition = NSCondition()
        private let capacity = 5
    
        func addItem(_ item: Int) {
            condition.lock()
            defer { condition.unlock() }
    
            while items.count == capacity {
                condition.wait() // Wait until there is free space
            }
    
            items.append(item)
            print("Added: \(item), Current Buffer: \(items)")
            condition.signal() // Signal waiting threads
        }
    
        func removeItem() -> Int {
            condition.lock()
            defer { condition.unlock() }
    
            while items.isEmpty {
                condition.wait() // Wait until an element appears
            }
    
            let item = items.removeFirst()
            print("Removed: \(item), Current Buffer: \(items)")
            condition.signal() // Signal waiting threads
            return item
        }
    }
    
  4. Grand Central Dispatch (GCD):

    • Serial Queues: Execute tasks sequentially, one at a time on the given thread, ensuring atomic access to resources.

    • Concurrent Queues with Barrier Flags: Allow concurrent reads, but write operations are executed exclusively. Implements a read-write lock model.

      import Foundation
      
      class ThreadSafeArray<Element> {
          private var array: [Element] = []
          private let queue =
          DispatchQueue(label: "com.mycompany.threadsafearray", attributes: .concurrent) // Concurrent queue
      
          func append(_ newElement: Element) {
              queue.async(flags: .barrier) { // Barrier for writing
                  self.array.append(newElement)
              }
          }
      
          func get(at index: Int) -> Element? {
              var result: Element?
              queue.sync { // Sync for reading (or async if read order doesn't matter)
                  if index < self.array.count {
                      result = self.array[index]
                  }
              }
              return result
          }
      }
      
  5. Core Data Context Operations: Each NSManagedObjectContext has its own private serial queue. Access to objects and the context should be performed only from this thread.

    import CoreData
    
    // Example of saving in a Core Data context
    let context = // Obtained context
    context.perform { // Execute on the context's private thread
        // Work with Core Data objects here
        do {
            try context.save()
        } catch {
            // Error handling
        }
    }
    

The choice of method depends on the specific task and performance requirements. GCD with serial queues or concurrent queues with barriers is often the most preferred approach due to its efficiency and ease of use. For Core Data, always use the perform or performAndWait methods.

How to synchronize data between threads and ensure… - sobes.tech