Crea una matriz segura para subprocesos en swift

Tengo un problema de enhebrado rápido. Tengo una matriz con algunos objetos en ella. En un delegado, la clase obtiene nuevos objetos cada segundo. Después de eso, tengo que comprobar si los objetos ya están en la matriz, así que tengo que actualizar el objeto, de lo contrario, tengo que eliminar / agregar el nuevo objeto.

Si agrego un nuevo objeto, primero tengo que buscar algunos datos en la red. Esto es Handelt a través de un bloque.

Ahora mi problema es cómo sincronizar estas tareas?

He intentado un dispatch_semaphore, pero este bloquea la IU, hasta que el bloque finalice.

También probé una variable bool simple, que comprueba si el bloque se está ejecutando actualmente y omite el método de comparación mientras tanto.

Pero ambos métodos no son ideales.

Cuál es la mejor manera de administrar la matriz, no quiero tener datos duplicados en la matriz.

Kirsteins es correcto, pero no siempre es necesario utilizar la cola de envío. Puedes usar:

objc_sync_enter(array) // manipulate the array objc_sync_exit(array) 

Esto debería hacer el truco. Para una bonificación adicional, puede crear una función para usar siempre que necesite seguridad de subprocesos:

 func sync(lock: NSObject, closure: () -> Void) { objc_sync_enter(lock) closure() objc_sync_exit(lock) } ... var list = NSMutableArray() sync (list) { list.addObject("something") } 

Tenga en cuenta que he cambiado AnyObject a NSObject . En Swift los tipos de colección se implementan como struct s y se pasan por valor , así que supongo que sería más seguro trabajar con clases de colección mutables que se transfieren por referencia cuando se usa la conveniente función de sync .

Actualización para Swift

El patrón recomendado para el acceso seguro a subprocesos está utilizando la barrier envío:

 let queue = DispatchQueue(label: "thread-safe-obj", attributes: .concurrent) // write queue.async(flags: .barrier) { // perform writes on data } // read var value: ValueType! queue.sync { // perform read and assign value } return value 

Mi enfoque para este problema fue el uso de la cola de envío en serie, para sincronizar el acceso a la matriz en caja. Bloqueará el hilo cuando intente obtener el valor en el índice y la cola esté realmente ocupada, pero ese también es el problema con los lockings.

 public class SynchronizedArray { private var array: [T] = [] private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL) public func append(newElement: T) { dispatch_async(self.accessQueue) { self.array.append(newElement) } } public subscript(index: Int) -> T { set { dispatch_async(self.accessQueue) { self.array[index] = newValue } } get { var element: T! dispatch_sync(self.accessQueue) { element = self.array[index] } return element } } } var a = SynchronizedArray() a.append(1) a.append(2) a.append(3) // can be empty as this is non-thread safe access println(a.array) // thread-safe synchonized access println(a[0]) println(a[1]) println(a[2]) 

La respuesta de Kirsteins es correcta, pero, por comodidad, he actualizado esa respuesta con las sugerencias de Amol Chaudhari y Rob para usar una cola simultánea con la barrera asíncrona para permitir lecturas simultáneas pero bloquear las escrituras.

También he envuelto algunas otras funciones de matriz que me han sido útiles.

 public class SynchronizedArray { private var array: [T] = [] private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_CONCURRENT) public func append(newElement: T) { dispatch_barrier_async(self.accessQueue) { self.array.append(newElement) } } public func removeAtIndex(index: Int) { dispatch_barrier_async(self.accessQueue) { self.array.removeAtIndex(index) } } public var count: Int { var count = 0 dispatch_sync(self.accessQueue) { count = self.array.count } return count } public func first() -> T? { var element: T? dispatch_sync(self.accessQueue) { if !self.array.isEmpty { element = self.array[0] } } return element } public subscript(index: Int) -> T { set { dispatch_barrier_async(self.accessQueue) { self.array[index] = newValue } } get { var element: T! dispatch_sync(self.accessQueue) { element = self.array[index] } return element } } } 

ACTUALIZAR Este es el mismo código, actualizado para Swift3.

 public class SynchronizedArray { private var array: [T] = [] private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess", attributes: .concurrent) public func append(newElement: T) { self.accessQueue.async(flags:.barrier) { self.array.append(newElement) } } public func removeAtIndex(index: Int) { self.accessQueue.async(flags:.barrier) { self.array.remove(at: index) } } public var count: Int { var count = 0 self.accessQueue.sync { count = self.array.count } return count } public func first() -> T? { var element: T? self.accessQueue.sync { if !self.array.isEmpty { element = self.array[0] } } return element } public subscript(index: Int) -> T { set { self.accessQueue.async(flags:.barrier) { self.array[index] = newValue } } get { var element: T! self.accessQueue.sync { element = self.array[index] } return element } } } 

Un detalle menor: en Swift 3 (al menos en XCode 8 Beta 6), la syntax de las colas cambió significativamente. Los cambios importantes en la respuesta de @Kirsteins serán:

 private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess") txAccessQueue.async() { // Your async code goes here... } txAccessQueue.sync() { // Your sync code goes here... } 

Creo que vale la pena investigar dispatch_barriers. El uso de gcd para la sincronicidad es más intuitivo para mí que utilizar la palabra clave de sincronización para evitar la mutación de estado de varios hilos.

https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html

Aquí hay una excelente respuesta, que es segura y no bloquea las lecturas concurrentes: https://stackoverflow.com/a/15936959/2050665

Está escrito en Objective C, pero portar a Swift es trivial.

 @property (nonatomic, readwrite, strong) dispatch_queue_t thingQueue; @property (nonatomic, strong) NSObject *thing; - (id)init { ... _thingQueue = dispatch_queue_create("...", DISPATCH_QUEUE_CONCURRENT); ... } - (NSObject *)thing { __block NSObject *thing; dispatch_sync(self.thingQueue, ^{ thing = _thing; }); return thing; } - (void)setThing:(NSObject *)thing { dispatch_barrier_async(self.thingQueue, ^{ _thing = thing; }); } 

Crédito a https://stackoverflow.com/users/97337/rob-napier

Enfoque:

Use DispatchQueue para sincronizar

Referir:

http://basememara.com/creating-thread-safe-arrays-in-swift/

Código:

A continuación se muestra una implementación básica de una matriz segura para subprocesos, puede ajustarla con precisión.

 public class ThreadSafeArray { private var elements : [Element] private let syncQueue = DispatchQueue(label: "Sync Queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) public init() { elements = [] } public init(_ newElements: [Element]) { elements = newElements } //MARK: Non-mutating public var first : Element? { return syncQueue.sync { elements.first } } public var last : Element? { return syncQueue.sync { elements.last } } public var count : Int { return syncQueue.sync { elements.count } } public subscript(index: Int) -> Element { get { return syncQueue.sync { elements[index] } } set { syncQueue.sync(flags: .barrier) { elements[index] = newValue } } } public func reversed() -> [Element] { return syncQueue.sync { elements.reversed() } } public func flatMap(_ transform: (Element) throws -> T?) rethrows -> [T] { return try syncQueue.sync { try elements.flatMap(transform) } } public func filter(_ isIncluded: (Element) -> Bool) -> [Element] { return syncQueue.sync { elements.filter(isIncluded) } } //MARK: Mutating public func append(_ element: Element) { syncQueue.sync(flags: .barrier) { elements.append(element) } } public func append(contentsOf newElements: S) where Element == S.Element, S : Sequence { syncQueue.sync(flags: .barrier) { elements.append(contentsOf: newElements) } } public func remove(at index: Int) -> Element? { var element : Element? syncQueue.sync(flags: .barrier) { if elements.startIndex ..< elements.endIndex ~= index { element = elements.remove(at: index) } else { element = nil } } return element } } extension ThreadSafeArray where Element : Equatable { public func index(of element: Element) -> Int? { return syncQueue.sync { elements.index(of: element) } } } 

en primer lugar, objc_sync_enter no funciona

 objc_sync_enter(array) defer { objc_sync_exit(array) } 

reason objc_sync_enter / objc_sync_exit no funciona con DISPATCH_QUEUE_PRIORITY_LOW

objc_sync_enter es una primitiva de nivel extremadamente bajo y no está destinada a ser utilizada directamente. Es un detalle de implementación del antiguo sistema @synchronized en ObjC.

para rápido, debería usarse así, como dijo @Kirsteins, y sugiero sincronización en lugar de asincrónica:

 private let syncQueue = DispatchQueue(label:"com.test.LockQueue") func test(){ self.syncQueue.sync{ // thread safe code here } } 

Para mejorar la respuesta aceptada , sugeriría utilizar defer:

 objc_sync_enter(array) defer { objc_sync_exit(array) } // manipulate the array 

y el segundo

 func sync(lock: NSObject, closure: () -> Void) { objc_sync_enter(lock) defer { objc_sync_exit(lock) } closure() }