Sobes.tech
Middle+

Como sincronizar dados entre threads e garantir a consistência do contexto?

sobes.tech IA

Resposta da IA

Para sincronizar dados e garantir a coerência do contexto numa aplicação multithread no iOS, utilizam-se as seguintes abordagens:

  1. NSLock / Lock: Mutex simples para bloquear secções críticas do código.

    import Foundation
    
    class ThreadSafeCounter {
        private var count: Int = 0
        private let lock = NSLock()
    
        func increment() {
            lock.lock()
            defer { lock.unlock() } // Liberação garantida do bloqueio
            count += 1
        }
    
        func getCount() -> Int {
            lock.lock()
            defer { lock.unlock() }
            return count
        }
    }
    
  2. NSRecursiveLock: Permite readquirir o bloqueio a partir do mesmo thread.

    import Foundation
    
    class RecursiveLocker {
        private let lock = NSRecursiveLock()
    
        func firstMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Primeiro método")
            secondMethod() // A reaquisição é possível
        }
    
        func secondMethod() {
            lock.lock()
            defer { lock.unlock() }
            print("Segundo método")
        }
    }
    
  3. NSCondition / NSConditionLock: Permitem que threads aguardem uma condição específica ou um valor antes de continuar. São utilizados para implementar o padrão produtor-consumidor.

    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() // Espera até haver espaço livre
            }
    
            items.append(item)
            print("Adicionado: \(item), Buffer atual: \(items)")
            condition.signal() // Sinaliza threads que aguardam
        }
    
        func removeItem() -> Int {
            condition.lock()
            defer { condition.unlock() }
    
            while items.isEmpty {
                condition.wait() // Espera até haver um elemento
            }
    
            let item = items.removeFirst()
            print("Removido: \(item), Buffer atual: \(items)")
            condition.signal() // Sinaliza threads que aguardam espaço
            return item
        }
    }
    
  4. Grand Central Dispatch (GCD):

    • Filas seriais: Executam tarefas sequencialmente, uma de cada vez na mesma thread, garantindo a atomicidade do acesso aos recursos.

    • Filas concorrentes com flags Barrier: Permitem executar leituras em paralelo, mas operações de escrita (write) são feitas de forma exclusiva. Isto implementa um modelo de bloqueio leitura-escrita.

      import Foundation
      
      class ThreadSafeArray<Element> {
          private var array: [Element] = []
          private let queue =
          DispatchQueue(label: "com.mycompany.threadsafearray", attributes: .concurrent) // Fila concorrente
      
          func append(_ newElement: Element) {
              queue.async(flags: .barrier) { // Barreira para escrita
                  self.array.append(newElement)
              }
          }
      
          func get(at index: Int) -> Element? {
              var result: Element?
              queue.sync { // Sincronização para leitura (ou async, se a ordem não for importante)
                  if index < self.array.count {
                      result = self.array[index]
                  }
              }
              return result
          }
      }
      
  5. Operações de Contexto do Core Data: Cada NSManagedObjectContext tem o seu próprio thread serial privado. O acesso a objetos e ao contexto deve ser feito apenas a partir desse thread.

    import CoreData
    
    // Exemplo de salvamento no contexto do Core Data
    let context = // Contexto obtido
    context.perform { // Execução no thread privado do contexto
        // Trabalhar com objetos do Core Data aqui
        do {
            try context.save()
        } catch {
            // Tratamento de erro
        }
    }
    

A escolha do método depende da tarefa específica e dos requisitos de desempenho. GCD com filas seriais ou filas concorrentes com barreiras costuma ser a abordagem mais preferida devido à sua eficiência e facilidade de uso. Para o Core Data, sempre utilize os métodos perform ou performAndWait.