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

时间:2014-01-29 17:52:52

标签: ios objective-c multithreading cocoa-touch core-data

我正在编写iOS应用的同步引擎。我正在编写的方法之一是重载数据功能,其中应用程序重新下载用户的数据以及他们的所有照片。这是一项昂贵的操作(按时间),因此我创建了一个NSOperation子类SSReloadDataOperation。它下载数据,获取currentUser实体,从当前用户中删除所有现有照片,并重新填充它。

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

如何使此操作具有线程安全性和原子性,以便我可以在不阻塞主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;
}

2 个答案:

答案 0 :(得分:1)

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

由于托管对象必须在使用其上下文的相同线程或串行队列中以及创建它们的位置使用,因此不可能在示例中使用currentUser之类的方法,并使其神奇地返回线程 - 安全管理对象。

但您可以将上下文作为参数传递。呼叫者将决定恢复用户的上下文。

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

以下是关于代码段的其他想法。

您在操作主体内创建的上下文可能是主要上下文的子项。然后,在保存此子项后,您将保存父项(如果此时您希望数据进入商店)。使用父子关系时,您不需要订阅did-save通知并合并其中的更改。

从技术上讲,您无法从操作的main访问主上下文的持久性存储协调器。因为它只允许使用来自一个线程或队列的上下文来工作(调用任何方法)。我打赌你在主线程上创建主要上下文。

答案 1 :(得分:0)

我认为你的问题就是这些问题;

     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!");