Sobes.tech
Middle+

Come sincronizzare i dati tra thread e garantire la coerenza del contesto?

sobes.tech AI

Risposta dell'AI

Per la sincronizzazione dei dati e per garantire la coerenza del contesto in un'app multithread su iOS, vengono utilizzati i seguenti approcci:

  1. NSLock / Lock: Mutex semplici per bloccare sezioni critiche del codice.

    import Foundation
    
    class ThreadSafeCounter {
        private var count: Int = 0
        private let lock = NSLock()
    
        func increment() {
            lock.lock()
            defer { lock.unlock() } // Rilascio garantito del lock
            count += 1
        }
    
        func getCount() -> Int {
            lock.lock()
            defer { lock.unlock() }
            return count
        }
    }
    
  2. NSRecursiveLock: Permette di riacquisire il lock dallo stesso thread.

    import Foundation
    
    class RecursiveLocker {
        private let lock = NSRecursiveLock()
    
        func firstMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Prima metodo")
            secondMethod() // La riacquisizione è possibile
        }
    
        func secondMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Seconda metodo")
        }
    }
    
  3. NSCondition / NSConditionLock: Permettono ai thread di attendere una condizione o un valore specifico prima di continuare. Sono usati per implementare il pattern producer-consumer.

    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() // Attende finché c'è spazio libero
            }
    
            items.append(item)
            print("Aggiunto: \(item), Buffer attuale: \(items)")
            condition.signal() // Segnala ai thread in attesa
        }
    
        func removeItem() -> Int {
            condition.lock()
            defer { condition.unlock() }
    
            while items.isEmpty {
                condition.wait() // Attende finché c'è un elemento
            }
    
            let item = items.removeFirst()
            print("Rimosso: \(item), Buffer attuale: \(items)")
            condition.signal() // Segnala ai thread in attesa di spazio
            return item
        }
    }
    
  4. Grand Central Dispatch (GCD):

    • Code di tipo seriale: Eseguono i task in sequenza, uno alla volta su un thread, garantendo l'accesso atomico alle risorse.

    • Code concorrenti con flag Barrier: Permettono di eseguire letture in modo concorrente, ma le operazioni di scrittura (write) vengono eseguite in modo esclusivo. Questo implementa un modello di lock di lettura-scrittura.

      import Foundation
      
      class ThreadSafeArray<Element> {
          private var array: [Element] = []
          private let queue =
          DispatchQueue(label: "com.mycompany.threadsafearray", attributes: .concurrent) // Queue concorrente
      
          func append(_ newElement: Element) {
              queue.async(flags: .barrier) { // Barrier per la scrittura
                  self.array.append(newElement)
              }
          }
      
          func get(at index: Int) -> Element? {
              var result: Element?
              queue.sync { // Sync per la lettura (o async, se l'ordine non è importante)
                  if index < self.array.count {
                      result = self.array[index]
                  }
              }
              return result
          }
      }
      
  5. Operazioni di Context di Core Data: Ogni NSManagedObjectContext ha il suo thread seriale privato. L'accesso agli oggetti e al contesto deve essere effettuato solo da questo thread.

    import CoreData
    
    // Esempio di salvataggio nel contesto di Core Data
    let context = // Context ottenuto
    context.perform { // Esecuzione sul thread privato del contesto
        // Lavorare con gli oggetti di Core Data qui
        do {
            try context.save()
        } catch {
            // Gestione dell'errore
        }
    }
    

La scelta del metodo dipende dal compito specifico e dai requisiti di prestazioni. GCD con code seriali o code concorrenti con barrier sono spesso le opzioni preferite per la loro efficienza e facilità d'uso. Per Core Data, assicurarsi sempre di usare i metodi perform o performAndWait.