«Colección fue mutado mientras enumerado» en executeFetchRequest

Estoy atascado en un problema para las horas, ahora y después de leer todo acerca de esto en stackoverflow (y aplicar todos los consejos encontrado), ahora estoy oficialmente en necesidad de ayuda. ;o)

Aquí es el contexto :

En mi iPhone proyecto, necesito importar datos sobre los antecedentes y la inserta en un contexto del objeto. Siguiendo los consejos que encontré aquí, aquí es lo que yo estoy haciendo :

  • Guardar el principal moc
  • Crear una instancia de un fondo moc con el almacén persistente coordinador utilizado por los principales moc
  • Registrar mi controlador como un observador de la NSManagedObjectContextDidSaveNotification de notificación para el fondo moc
  • Llamar al método de importación en un subproceso en segundo plano
  • Cada vez recibidos los datos, se inserta en el fondo moc
  • Una vez que todos los datos han sido importados, guardar el fondo moc
  • Combinar los cambios en los principales moc, en el hilo principal
  • Anular el registro de mi controlador como un observador de la notificación
  • Reset y suelte el fondo moc

A veces (al azar), la excepción…

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

…se produce cuando llamo executeFetchRequest en el fondo moc, para comprobar si la importación de datos ya existe en la base de datos. Me pregunto ¿cuál es la mutación del conjunto ya que no hay nada que se ejecutan fuera el método de importación.

He incluido el código completo de mi controlador y mi prueba de entidad (mi proyecto consiste en estas dos clases y la aplicación de delegado, que ha sido sin modificar) :

//
// RootViewController.h
// FK1
//
// Created by Eric on 09/08/10.
// Copyright (c) 2010 __MyCompanyName__. All rights reserved.
//


#import <CoreData/CoreData.h>

@interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate> {
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectContext *backgroundMOC;
}


@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSManagedObjectContext *backgroundMOC;

@end


//
// RootViewController.m
// FK1
//
// Created by Eric on 09/08/10.
// Copyright (c) 2010 __MyCompanyName__. All rights reserved.
//


#import "RootViewController.h"
#import "FK1Message.h"

@implementation RootViewController

@synthesize managedObjectContext;
@synthesize backgroundMOC;

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationController.toolbarHidden = NO;

    UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshAction:)];

    self.toolbarItems = [NSArray arrayWithObject:refreshButton];
}

#pragma mark -
#pragma mark ACTIONS

- (void)refreshAction:(id)sender {
    //If there already is an import running, we do nothing

    if (self.backgroundMOC != nil) {
        return;
    }

    //We save the main moc

    NSError *error = nil;

    if (![self.managedObjectContext save:&error]) {
        NSLog(@"error = %@", error);

        abort();
    }

    //We instantiate the background moc

    self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];

    [self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    //We call the fetch method in the background thread

    [self performSelectorInBackground:@selector(_importData) withObject:nil];
}

- (void)_importData {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundMOCDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC];         

    FK1Message *message = nil;

    NSFetchRequest *fetchRequest = nil;
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC];
    NSPredicate *predicate = nil;
    NSArray *results = nil;

    //fake import to keep this sample simple

    for (NSInteger index = 0; index < 20; index++) {
        predicate = [NSPredicate predicateWithFormat:@"msgId == %@", [NSString stringWithFormat:@"%d", index]];

        fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

        [fetchRequest setEntity:entity];
        [fetchRequest setPredicate:predicate];

        //The following line sometimes randomly throw the exception :
        //*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5b71a00> was mutated while being enumerated.

        results = [self.backgroundMOC executeFetchRequest:fetchRequest error:NULL];

        //If the message already exist, we retrieve it from the database
        //If it doesn't, we insert a new message in the database

        if ([results count] > 0) {
            message = [results objectAtIndex:0];
        }
        else {
            message = [NSEntityDescription insertNewObjectForEntityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC];
            message.msgId = [NSString stringWithFormat:@"%d", index];
        }

        //We update the message

        message.updateDate = [NSDate date];
    }

    //We save the background moc which trigger the backgroundMOCDidSave: method

    [self.backgroundMOC save:NULL];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC];

    [self.backgroundMOC reset]; self.backgroundMOC = nil;

    [pool drain];
}

- (void)backgroundMOCDidSave:(NSNotification*)notification {    
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundMOCDidSave:) withObject:notification waitUntilDone:YES];
        return;
    }

    //We merge the background moc changes in the main moc

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

@end

//
// FK1Message.h
// FK1
//
// Created by Eric on 09/08/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import <CoreData/CoreData.h>

@interface FK1Message :  NSManagedObject  
{
}

@property (nonatomic, retain) NSString * msgId;
@property (nonatomic, retain) NSDate * updateDate;

@end

//
// FK1Message.m
// FK1
//
// Created by Eric on 09/08/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "FK1Message.h"

@implementation FK1Message 

#pragma mark -
#pragma mark PROPERTIES

@dynamic msgId;
@dynamic updateDate;

@end

Esto es todo ! El proyecto completo está aquí. No se vista de tabla, no NSFetchedResultsController, nada más que un subproceso en segundo plano que importar los datos en un fondo moc.

Lo que podría mutar el conjunto, en este caso ?

Estoy bastante seguro de que me estoy perdiendo algo que es obvio y me está volviendo loco.

EDICIÓN:

Aquí es el completo seguimiento de la pila :

    2010-08-10 10:29:11.258 FK1[51419:1b6b] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5d075b0> was mutated while being enumerated.<CFBasicHash 0x5d075b0 [0x25c6380]>{type = mutable set, count = 0,
entries =>
}
'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x0255d919 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x026ab5de objc_exception_throw + 47
    2   CoreFoundation                      0x0255d3d9 __NSFastEnumerationMutationHandler + 377
    3   CoreData                            0x02287702 -[NSManagedObjectContext executeFetchRequest:error:] + 4706
    4   FK1                                 0x00002b1b -[RootViewController _fetchData] + 593
    5   Foundation                          0x01d662a8 -[NSThread main] + 81
    6   Foundation                          0x01d66234 __NSThread__main__ + 1387
    7   libSystem.B.dylib                   0x9587681d _pthread_start + 345
    8   libSystem.B.dylib                   0x958766a2 thread_start + 34
)
terminate called after throwing an instance of 'NSException'
  • En Xcode del menú Ejecutar, a su vez en «Parada en Objective-C Excepciones», a continuación, ejecute la aplicación en el Depurador. ¿Qué te parece?
  • Se confirma que el bloqueo de la aplicación en el «executeFetchRequest:error:» la línea. He añadido el completo seguimiento de la pila a mi pregunta original…
  • ¿Y qué acerca de los otros hilos?
  • Hmmm, aquí está el hilo principal de la pila : #0 0x958490fa en mach_msg_trap #1 0x95849867 en mach_msg #2 0x0253f206 en __CFRunLoopServiceMachPort #3 0x0249c8b4 en __CFRunLoopRun #4 0x0249c280 en CFRunLoopRunSpecific #5 0x0249c1a1 en CFRunLoopRunInMode #6 0x027a82c8 en GSEventRunModal #7 0x027a838d en GSEventRun #8 0x00021b58 en UIApplicationMain #9 0x00001edc en principal a principal.m:16 Hay otros 2 hilos (libdispatch-manager y «WebThread»), pero que no dan más información.
InformationsquelleAutor Eric MORAND | 2010-08-10

2 Kommentare

  1. 180

    OK, creo que he resuelto mi problema y tengo que agradecer a este blog de Fred McCann s :

    http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

    El problema parece venir del hecho de que yo instancias de mi fondo de moc en el hilo principal en lugar del subproceso en segundo plano. Cuando Apple dice que cada hilo tiene que tener su propio moc, usted tiene que tomar muy en serio : cada moc debe ser instanciado en el hilo que se va a usar !

    Mover las siguientes líneas…

    //We instantiate the background moc
    
    self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];
    
    [self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    …en el _importData método (justo antes de registrar el controlador como observador de la notificación) resuelve el problema.

    Gracias por tu ayuda, Pedro. Y gracias a Fred McCann por su valiosa blog !

    • OK, después de un montón de pruebas, puedo confirmar que esta absolutamente resuelto mi problema. Voy a marcar esta tan aceptado por la respuesta tan pronto como se me permite…
    • Gracias por esta solución. Este hilo tiene una muy buena aplicación de bloqueo/desbloqueo de contexto para evitar conflictos durante la mezcla: stackoverflow.com/questions/2009399/…
    • +1 muchas Gracias por poner la pregunta, la solución y proporciona el enlace para Fred McCann del blog.. me ha ayudado mucho!!!
    • each moc must be instantiated in the thread that will be using it Yo, sin embargo, sólo la operación en MOC debe estar en el mismo hilo, pero la creación de la MOC en sí demasiado, si este es un MOC, los relacionados con la cola aún no existe ..
    • Tengo la misma pregunta aquí. ¿Cómo se puede crear una instancia del contexto en el hilo que se va a usar? El hilo es que no existe todavía. Estoy usando Swift y no entiendo que es lo que se mueve en la _importData método » significa.
  2. 0

    Yo estaba trabajando en la importación de registro de & visualización de los registros en formato tableview. Se enfrentan el mismo problema cuando intentaba guardar registro en backgroundThread como el de abajo,

     [self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];

    mientras que ya ha creado un PrivateQueueContext. Sólo reemplace el código de arriba con la de abajo una

    [self saveObjectContextInDataBaseWithContext:privateQueueContext];

    Realmente era mi trabajo necio para ahorrar en el subproceso en segundo plano, mientras que ya ha creado un privateQueueConcurrencyType para guardar el registro.

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea