Problèmes de threading de données de base et de conflit de verrous

J'écris actuellement le moteur de synchronisation de mon application iOS. L'une des methods que j'écris est une fonction de rechargement de données, dans laquelle l'application télécharge à nouveau datatables de l'user ainsi que toutes leurs photos. C'est une opération coûteuse (dans le time), j'ai donc créé une sous-class SSReloadDataOperation , SSReloadDataOperation . Il télécharge datatables, obtient l'entité currentUser, supprime toutes les photos existantes de cet user currentUser et le repeuple.

Cependant, même si je pensais que c'était thread-safe, parfois, alors que l'opération est en cours et -currentUser est accessible à partir d'ailleurs, l'application se bloque, probablement en essayant de le récupérer. D'autres fois, l'interface user se fige parfois, et une pause dans le débogueur montre qu'il se bloque toujours lors d'un appel d'exécution -currentUser NSFetchRequest .

Comment rendre cette opération thread-safe et atomique, de sorte que je peux download et repeupler sans bloquer le thread d'interface user principal, et toujours -currentUser être accessible? Y a-t-il quelque chose qui me manque en termes d'utilisation de serrures ou d'architecture? Je vous remercie!

Code:

 - (void)main { // Download the photo data [[SyncEngine defaultEngine] getUserPhotosWithCompletionBlock:^(NSMutableArray *photos) { if (photos) { // Create a new NSManagedObjectContext for this operation SSAppDelegate* appDelegate = [[UIApplication sharedApplication] delegate]; NSManagedObjectContext* localContext = [[NSManagedObjectContext alloc] init]; [localContext setPersistentStoreCoordinator:[[appDelegate managedObjectContext] persistentStoreCoordinator]]; NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:localContext]; NSError* error; NSFetchRequest* request = [[[SyncEngine defaultEngine] managedObjectModel] fetchRequestFromTemplateWithName:@"CurrentUser" substitutionVariables:[[NSDictionary alloc] init]]; User* currentUser = [[localContext executeFetchRequest:request error:&error] objectAtIndex:0]; // Remove the old data [currentUser setPhotos:[[NSSet alloc] init]]; // Iterate through photo data, repopulate for (Photo* photo in photos) { [currentUser addPhotosObject:photo]; } if (! [localContext save:&error]) { NSLog(@"Error saving: %@", error); } NSLog(@"Completed sync!"); } } userId:[[[SyncEngine defaultEngine] currentUser] userId]]; } 

méthode de commodité -currentUser, généralement appelée à partir du thread principal.

 - (User *)currentUser { NSError* error; NSFetchRequest* request = [self.managedObjectModel fetchRequestFromTemplateWithName:@"CurrentUser" substitutionVariables:[[NSDictionary alloc] init]]; NSArray* result = [self.managedObjectContext executeFetchRequest:request error:&error]; if ([result count] == 1) { return [result objectAtIndex:0]; } return nil; } 

Vous avez raison, ce gel ressemble à un problème de threading.

Comme les objects gérés doivent être utilisés dans le même thread ou la même queue où leur context est utilisé et où ils ont été créés, il est impossible d'utiliser une méthode comme currentUser de l'exemple et de la renvoyer comme par magie.

Mais vous pouvez passer le context en paramètre. L'appelant décidera dans quel context ressusciter l'user.

 - (User *)userWithContext:(NSManagedContext *)context { User *user = nil; NSError *error; NSFetchRequest *request = ...; NSArray *userArray = [context executeFetchRequest:request error:&error]; if (userArray == nil) { NSLog(@“Error fetching user: %@“, error); } else if ([userArray count] > 0) { user = userArray[0]; } return user; } 

Et voici d'autres reflections sur vos extraits de code.

Le context que vous créez dans la partie principale de votre opération peut être un enfant de votre context principal. Ensuite, après avoir enregistré cet enfant, vous enregistrez le parent (si vous voulez que datatables soient stockées dans le magasin à ce stade). Lorsque vous utilisez une relation parent-enfant, vous n'avez pas besoin de vous abonner à la notification did-save et d'en merge les modifications.

Techniquement, vous ne pouvez pas accéder au coordinateur de magasin persistant de votre context principal à partir du principal de l'opération. Parce qu'il est seulement autorisé à travailler (appeler toutes les methods) avec des contexts d'un thread ou d'une queue. Et je parie que vous créez votre context principal sur le fil principal.

Votre problème est ces lignes, je pense;

  NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:localContext]; 

Parce que les notifications se triggersnt dans le thread dans lequel la notification se produit, votre méthode -mergeChanges: est appelée dans le thread d'arrière-plan. Je suppose qu'il accède à self.managedObjectContext et ajoute les objects, mais il le fait à partir d'un thread d'arrière-plan. Donc, yah, ça va planter ou se bloquer.

Vous n'avez pas du tout besoin de vous inscrire à cette notification, car vous ne sauvegardez ce context qu'une seule fois. Vous pouvez simplement attendre jusqu'à ce que vous appeliez save, puis planifier une fusion: sur le thread principal . Comme ça:

  if (![localContext save:&error]) NSLog(@"Error saving: %@", error); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self mergeChanges:nil]; }]; NSLog(@"Completed sync!");