核心数据线程和locking争用问题

我目前正在编写iOS应用程序的同步引擎。 我正在编写的一种方法是重新加载数据function,在该function中,应用程序会重新下载用户的数据以及所有照片。 这是一个昂贵的操作(时间上),所以我创build了一个NSOperation子类SSReloadDataOperation 。 它下载数据,获取当前用户实体,从当前用户中删除所有现有的照片,并重新填充。

然而,尽pipe我认为这是线程安全的,但是有时在操作正在运行并且-currentUser从其他地方访问时,应用程序崩溃,大概是在尝试获取它时。 其他时候,UI有时会冻结,并在debugging器中暂停显示它总是停在-currentUser NSFetchRequest执行调用。

我如何使这个操作线程安全和primefaces性,使我可以下载和重新填充,而不阻塞主UI线程,仍然有-currentUser可访问? 在使用锁或体系结构方面有什么缺失的吗? 谢谢!

码:

 - (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]]; } 

-currentUser方便的方法,通常从主线程调用。

 - (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; } 

你是对的,这个冻结感觉像是一个线程问题。

由于托pipe对象必须在使用上下文的同一个线程或串行队列中以及创build它们的位置中使用,因此不可能像示例中那样具有currentUser方法,并使其神奇地返回线程安全的托pipe对象。

但是您可以将上下文作为parameter passing。 调用者将决定在哪个上下文中重新build立用户。

 - (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; } 

这里有关于你的代码片断的其他想法。

您在操作主体中创build的上下文可能是您的主要上下文的子项。 然后,保存这个孩子后,你保存父母(如果你想要数据进入商店在这一点上)。 在使用父子关系时,您不需要订阅“保存”通知,并从中合并更改。

从技术上讲,你不能从操作主体访问主要上下文的持久存储协调器。 因为只允许从一个线程或队列中处理(调用任何方法)上下文。 我敢打赌,你在主线上创build主要的上下文。

我想,你的问题就是这些问题。

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

由于通知在发生通知的线程中触发,所以在后台线程中正在调用-mergeChanges:方法。 我假设它访问self.managedObjectContext并添加对象,但它是从后台线程。 所以呀,它会崩溃或挂起。

您根本不需要注册此通知,因为您只保存一次该上下文。 您可以等到您调用save,然后在主线程上安排合并: 喜欢这个:

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