En Swift, para garantizar la seguridad de los hilos, se suelen utilizar GCD (Grand Central Dispatch) y colas (DispatchQueue), NSLock, así como mecanismos modernos como actor (a partir de Swift 5.5).
La idea principal es que el acceso a los datos mutables se realiza ya sea estrictamente a través de colas secuenciales o utilizando sincronizadores (bloqueos).
Ejemplo con GCD:
class ThreadSafeArray<Element> { private var array: [Element] = [] private let queue = DispatchQueue(label: "com.example.arrayQueue", attributes: .concurrent) func append(_ item: Element) { queue.async(flags: .barrier) { self.array.append(item) } } func get(index: Int) -> Element? { var result: Element? queue.sync { result = self.array.indices.contains(index) ? self.array[index] : nil } return result } }
Enfoque moderno: Actors (Swift 5.5+):
actor SafeCounter { private var value = 0 func increment() { value += 1 } func get() -> Int { return value } }
Ambos enfoques permiten evitar condiciones de carrera y mantener la coherencia del estado.
Pregunta:
¿Qué sucederá si usas
DispatchQueue.syncdentro de la Main Queue y por qué?
Respuesta: Se producirá un deadlock (bloqueo), porque la Main Queue ya está ejecutando una tarea y está esperando que termine una operación síncrona en la misma cola. Por lo tanto, la cola no podrá procesar la siguiente tarea hasta que la actual termine, lo cual nunca sucederá.
Ejemplo:
DispatchQueue.main.sync { // Deadlock: esta línea nunca se ejecutará }
Historia
En el proyecto se utilizó una colección mutable, a la que se accedía simultáneamente desde varios hilos sin sincronización. Esto provocaba caídas de la aplicación y errores difíciles de detectar debido a condiciones de carrera: a veces se perdían elementos y, en ocasiones, se producía un desbordamiento de matriz.
Historia
Un desarrollador utilizó incorrectamente DispatchQueue.sync en la cola principal durante el tiempo de ejecución, lo que provocó un deadlock y una parada total de la interfaz de usuario para los usuarios tras una actualización. Se requería una corrección urgente y un retroceso de la versión.
Historia
Al intentar hacer que una clase fuera segura para subprocesos utilizando NSLock, se olvidaron de implementar el bloqueo/desbloqueo para todos los caminos de salida del método (por ejemplo, en caso de error o retorno dentro de guard), lo que provocaba potenciales deadlocks y serios problemas de rendimiento en la aplicación.