在后台创建的 NSManagedObject 变成了主线程上的错误

Posted

技术标签:

【中文标题】在后台创建的 NSManagedObject 变成了主线程上的错误【英文标题】:NSManagedObjects created on background turning into faults on main thread 【发布时间】:2017-03-30 11:24:47 【问题描述】:

我正在处理一个奇怪的案例。我从 API 获取一些数据,将响应 JSON 转换为 NSManagedObjects 并保存它们,所有这些都在后台线程中并使用父级为 NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType 上下文。

在创建 NSManagedObjects 的同时,我将它们放在一个数组中,以便通过在主线程上执行的完成块在我的视图控制器中使用它。这是我正在做的简化版本。

- (void)getObjectFromAPIWithCompletion:((^)(NSArray *objects, NSError *error))completion

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^

    NSManagedObjectContext *backgroundMoc = [[CoreDataStack sharedManager] newChildContextWithConcurrencyType:NSPrivateQueueConcurrencyType];

    NSArray *items = [self processToParseResponseFromAPIAndSaveInCoreDataUsingContext:backgroundContext];
    [backgroundMoc save:nil];


    dispatch_async(dispatch_get_main_queue(), ^
      //Beyond this point, all properties of items are nil or 0 (if integer)
      if(completion) completion(items, error);
    );

  );

奇怪的事情发生在完成块内部。我在该块的第一行放置了一个断点并在控制台上打印items 数组。一切看起来都很好,所有项目(在本例中为 50 个)及其属性都正常。

问题是,在那一行之后(我没有更改 items 数组上的任何内容)所有对象都变成错误并且我没有获得任何有关属性的数据。

不知道这种罕见行为的原因是什么。有什么想法吗?

谢谢。

编辑:Hal 的回答让我走上了正轨,所以这就是我为解决这个问题所做的工作。就在调用将在主线程上执行的块之前,我将托管对象从后台上下文“移动”到主上下文,并使用它们的 objectID 获取。像这样:

NSArray *objects = //Objects created on background thread with Private queue

NSMutableArray *objectsIDs = [NSMutableArray array];
for (Object *object in object) 
  [objectsIDs addObject:object.objectID];


//Save on private managed object context and on completion...
[self.managedObjectContext saveWithCompletion:^(NSError *error) 

  NSManagedObjectContext *mainMOC = [[CoreDataManager sharedManager] mainContext];
  NSMutableArray *fetchedObjects = [NSMutableArray array];
  for (NSManagedObjectID *objectID in objectsIDs) 
    [fetchedArticles addObject:[mainMOC objectWithID:objectID]];
  

  if (completion) completion(fetchedObjects, pagination, nil);
];

这样,所有对象都不是主线程上的故障。

【问题讨论】:

【参考方案1】:

如果这些对象(items 的内容)已经在后台线程中创建,则不能在主线程中使用它们。您不能在线程之间传递 NSManagedObject 实例。

您必须传递 NSManagedObjectID(在您保存后台 MOC 之前它不会是永久性的),或者在主线程上使用 NSFetchedResultsController(指向主 MOC)。这是 NSFetchedResultsController 设计的一种情况,所以我建议你走这条路。

【讨论】:

以上是关于在后台创建的 NSManagedObject 变成了主线程上的错误的主要内容,如果未能解决你的问题,请参考以下文章

将 NSManagedObject(在主上下文中创建)从后台线程传递到主线程是不是安全?

NSManagedObject:在单独的线程上创建

核心数据在后台插入大数据

Core Data,在后台线程中修改 NSManagedObject

当我对它有很强的参考时,NSManagedObject 会变成故障吗?

将 NSManagedObject 变成部分错误,其中一些字段为 nil,而另一些则不是