0

我发布的应用程序有以下崩溃报告:

在此处输入图像描述

synchronizeMyWords方法从数据库中获取实体,使用主上下文父级创建私有队列上下文,最后保存结果。所有操作都在后台线程中。每次应用程序进入backgroundand时都会调用此方法foreground。这是一个简化的方法:

- (AWSTask *)synchronizeMyWords {
  __weak typeof(self) weakSelf = self;

  AWSContinuationBlock block = ^id _Nullable(AWSTask * _Nonnull task) {
    if ([task.result isKindOfClass:[NSArray class]]) {
      NSArray * records = (NSArray *)task.result;
      NSManagedObjectContext * context = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
      [context performBlockAndWait:^{
        for (NSDictionary * info in records) {
            [RDRWord MR_createEntityInContext:context];
        }

        [context save:nil];
      }];
      return [AWSTask taskWithResult:@YES];
    }
    return [AWSTask taskWithError:[NSError errorWithDomain:@"" code:404 userInfo:nil]];
  };

  AWSExecutor * executor = [AWSExecutor defaultExecutor];


  return [[self loadLocalWords] continueWithExecutor:executor withBlock:block];
}

如您所见,我正在使用Magical Record 3rd 方库来管理 Core Data 堆栈。这是创建私有队列上下文的方法:

+ (NSManagedObjectContext *) MR_contextWithParent:(NSManagedObjectContext *)parentContext
{
    NSManagedObjectContext *context = [self MR_newPrivateQueueContext];
    [context setParentContext:parentContext];
    [context MR_obtainPermanentIDsBeforeSaving];
    return context;
}

你可以在这里NSManagedObjectContext+MagicalRecord查看github 上的整个类别。

在脱离作用域之前释放的context内部对象如何可用?performBlockAndWait:我个人无法重现崩溃,但我的很多用户(iOS 8.1 - 10 设备)都受到此问题的影响。

更新 1:

这是例如博客上的相同报告

4

2 回答 2

2

我将@Mundi 的答案标记为正确,因为他写了您应该遵循的一般方法。现在,我想在这里分享我是如何调试它的。首先,我了解到,可以在 xcode 中打开调试并发断言。您需要在启动时传递以下参数:

-com.apple.CoreData.ConcurrencyDebug 1

在此处输入图像描述

现在,在您的应用程序输出中,您应该会看到日志消息:

2016-12-12 01:58:31.665 your-app[4267:2180376] CoreData: annotation: Core Data multi-threading assertions enabled.

一旦我打开它,我的应用程序在synchronizeMyWords方法中崩溃(老实说,不仅在那里。想知道,为什么 Apple 在调试模式下默认不包含并发断言?)。我检查了defaultExecutorAWSCore中的内容并看到了这个:

+ (instancetype)defaultExecutor {
    static AWSExecutor *defaultExecutor = NULL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        defaultExecutor = [self executorWithBlock:^void(void(^block)()) {
            // We prefer to run everything possible immediately, so that there is callstack information
            // when debugging. However, we don't want the stack to get too deep, so if the remaining stack space
            // is less than 10% of the total space, we dispatch to another GCD queue.
            size_t totalStackSize = 0;
            size_t remainingStackSize = remaining_stack_size(&totalStackSize);

            if (remainingStackSize < (totalStackSize / 10)) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
            } else {
                @autoreleasepool {
                    block();
                }
            }
        }];
    });
    return defaultExecutor;
}

根据他们的if说法,我continuationBlock不能保证在DISPATCH_QUEUE_PRIORITY_DEFAULT队列中执行。因此,我创建了一个共享dispatch_queue_t队列,并结合performBlockAndWait:CoreData 方法调用它的所有操作。结果,现在没有崩溃,我提交了新版本。如果我没有收到任何context僵尸崩溃报告,我将更新这篇文章。

于 2016-12-12T06:02:22.127 回答
1

Core Data 提供了充足的 API 来处理后台线程。这些也可以通过 Magical Record 访问。

看起来好像您不必要地创建了太多线程。我认为雇佣AWSContinuationBlockAWSExecutor不是一个好主意。synchronizeMyWords可以从后台线程调用。该块可能在后台线程上运行。在块内,您创建一个链接到子上下文的新后台线程。目前尚不清楚loadLocalWords返回什么,或者如何continueWithExecutor:block:处理线程。

数据的保存也有问题。子上下文保存后不保存主上下文;大概这会在以后发生,但可能与其他一些操作有关,因此您的代码之前工作的事实可能更像是“误报”。

我的建议是简化线程代码。您应该将自己限制在 Core Data 块 API。

于 2016-12-10T09:31:09.460 回答