La matriz fue mutada mientras se enumeraba – Swift

func mapView(mapView: MKMapView!, regionDidChangeAnimated animated: Bool) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { if (self.busStops.count > 0) { if (mapView.camera!.altitude <= 1000) { for (var i = 0; i < self.busStops.count; i++) { if (MKMapRectContainsPoint(mapView.visibleMapRect, MKMapPointForCoordinate(self.busStops[i].position))) { let stop = BusAnno() stop.setCoordinate(self.busStops[i].position) stop.type = "stop" stop.title = self.busStops[i].name stop.subtitle = self.busStops[i].street self.activeStops.append(stop) } } dispatch_async(dispatch_get_main_queue()) { self.mapView.addAnnotations(self.activeStops) } } else if (self.activeStops.count > 0) { mapView.removeAnnotations(self.activeStops) self.activeStops = [] } } } } 

El código anterior me da actualmente:

 Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x1775edd0> was mutated while being enumerated.' 

La razón por la que esto sucede es porque si el usuario aleja rápidamente mientras la aplicación sigue agregando paradas de bus, se compromete con el error "mutado mientras se enumeran". El problema es que no estoy seguro de cómo solucionar esto, básicamente necesito comprobar si la aplicación ha terminado de agregar paradas de bus antes de eliminarlas.

El objective de este código es agregar paradas de bus al map cuando se hace un zoom en less de 1000 metros y luego eliminar las paradas del bus cuando se superan esta altitud sin get este error.

Me temo que el "mutado mientras se enumeran" no es su mayor problema. También está disparando nuevos hilos en rápida sucesión, porque mapView: regionDidChange: se puede invocar en rápida sucesión durante el zoom / paneo. De la reference de class :

Este método se invoca cuando cambia la región del map que se muestra actualmente. Durante el desplazamiento, este método puede llamarse muchas veces para informar actualizaciones a la position del map.

Como consecuencia, también está agregando las mismas annotations varias veces al map.

Su algorithm debe ser similar al siguiente. Tenga en count que esto es solo un boceto, no maneja la eliminación y es probable que ni siquiera sea Swift válido, nunca he progtwigdo en el idioma:

 func mapView(mapView: MKMapView!, regionDidChangeAnimated animated: Bool) { if (isUpdating) { return; isUpdating = true; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { var currentRect; do { currentRect = mapView.visibleMapRect; var toAdd = []; for (var i = 0; i < self.busStops.count; i++) { if (!self.busStops[i].isOnMap && MKMapRectContainsPoint(currentRect, MKMapPointForCoordinate(self.busStops[i].position))) { // create stop toAdd.append(stop); self.busStops[i].isOnMap = true; } } dispatch_async(dispatch_get_main_queue()) { self.mapView.addAnnotations(toAdd) } } while (mapView.visibleMapRect != currentRect); isUpdating = false; } } 

El problema se refiere a la security de los hilos. Su matriz busStops está siendo modificada por 2 subprocesss al mismo time.

Por lo tanto, debe sincronizar el acceso a este, es decir, asegurarse de que las actualizaciones de la matriz busStops se produzcan en serie (una tras otra) en lugar de simultáneamente (al mismo time). Una forma de hacerlo es enviar todas las modificaciones de esa matriz a una queue en serie que cree.

dispatch_get_global_queue despachará la lógica anterior a una queue simultánea global compartida. En lugar de utilizar una queue global, cree la suya propia y guárdela en una variable de instancia en la class.

 _queue = dispatch_queue_create("com.app.serialQueue", DISPATCH_QUEUE_SERIAL); 

y luego envíe el trabajo según sea necesario:

 dispatch_async(_queue, ^{ // Work, ie modifications to busStops array }); 

Si desea get más matices, puede configurar su queue como una queue simultánea, y usar dispatch_async para todas las lecturas en la matriz busStops , y usar dispatch_barrier_async para todas las escrituras. Este último hace que la queue se comporte temporalmente como una queue en serie.

El problema se debe a que varios hilos modifican el mismo object al mismo time.

Hay dos maneras de solucionar este problema:

1) Método 1

Utilizar:

 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) 

En lugar de:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) 

Pero no lo recomendaré de esta manera, ya que puede congelar su hilo principal.


2) Método 2

Use la queue de serie personalizada dispatch_barrier_async e implemente el getter y el configurador personalizados para la matriz.

Declare una propiedad como:

 // For handling the read-write problem @property (nonatomic, strong) dispatch_queue_t myQueue; 

En su vista, viewDidLoad inicializa como:

 _myQueue = dispatch_queue_create("com.midhun.mp.myQueue", DISPATCH_QUEUE_CONCURRENT); 

Y implementa tus methods como:

 func mapView(mapView: MKMapView!, regionDidChangeAnimated animated: Bool) { dispatch_barrier_async(_myQueue, ^{ if (self.busStops.count > 0) { if (mapView.camera!.altitude <= 1000) { for (var i = 0; i < self.busStops.count; i++) { if (MKMapRectContainsPoint(mapView.visibleMapRect, MKMapPointForCoordinate(self.busStops[i].position))) { let stop = BusAnno() stop.setCoordinate(self.busStops[i].position) stop.type = "stop" stop.title = self.busStops[i].name stop.subtitle = self.busStops[i].street self.activeStops.append(stop) } } dispatch_async(dispatch_get_main_queue()) { self.mapView.addAnnotations(self.activeStops) } } else if (self.activeStops.count > 0) { mapView.removeAnnotations(self.activeStops) self.activeStops = [] } } } } 

Y cambie su método de eliminación de datos también está utilizando dispatch_barrier_async(_myQueue, ^{});

Cuando solo miro la pregunta, este es un problema de security de hilo.

Puede usar la comunicación entre hilos para permitir que un hilo conozca el estado de otro hilo. Por ejemplo, configurando una variable a través del sistema de notificación.

Entonces, en el hilo de carga, usted dispara un post de notificación tan pronto como se cargan todas las paradas del bus.

En la rutina de recepción no hace nada (sin zoom ni lo que sea) antes de recibir la Notificación.

Puede sincronizar el acceso a self.activeStops como otros han mencionado, pero esta es otra solución fácil también:

Puede agregar sus objects BusAnno a una matriz temporal dentro del hilo (asumo que iterating es la parte más costosa del código) y luego los descarga a la variable self.activeStops en el hilo principal (simplemente agregando objects existentes a otra matriz será rápida).

De esta forma, solo tocará self.activeStops en el hilo principal, pero aún obtendrá los beneficios de subprocesss para actualizarlo.

También lo hice para que la vista del map no se toque dentro del hilo, porque es un elemento de interfaz de usuario

NOTA: No soy un experto en Swift, por lo que es posible que necesite algunos ajustes

 func mapView(mapView: MKMapView!, regionDidChangeAnimated animated: Bool) { // Take a snapshot of the altitude and visibleMapRect so we're not accessing the map view in the thread let altitude = mapView.camera!.altitude let visibleMapRect = mapView.visibleMapRect dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { if (self.busStops.count > 0) { if (altitude <= 1000) { // New bus stops added here in the thread var newActiveStops = [BusAnno]() for (var i = 0; i < self.busStops.count; i++) { if (MKMapRectContainsPoint(visibleMapRect, MKMapPointForCoordinate(self.busStops[i].position))) { let stop = BusAnno() stop.setCoordinate(self.busStops[i].position) stop.type = "stop" stop.title = self.busStops[i].name stop.subtitle = self.busStops[i].street newActiveStops.append(stop) } } dispatch_async(dispatch_get_main_queue()) { // Add to active stops on main thread self.activeStops += newActiveStops self.mapView.addAnnotations(self.activeStops) } } else { dispatch_async(dispatch_get_main_queue()) { if (self.activeStops.count > 0) { // Also moved all map updates to the main thread (it's a UI element) mapView.removeAnnotations(self.activeStops) self.activeStops = [] } } } } } }