xCode 7.0 IOS9 SDK:使用performBlockAndWait执行获取请求时发生死锁

xCode 7.0 IOS9 SDK:使用performBlockAndWait执行获取请求时发生死锁,ios,core-data,deadlock,ios9,magicalrecord,Ios,Core Data,Deadlock,Ios9,Magicalrecord,更新:我已经准备好了样本,该样本是复制没有神奇记录的问题。请使用以下URL下载测试项目: 提供的项目存在以下问题:获取时死锁 在从主线程调用的performBlockAndWait中 如果代码是使用XCode版本>6.4编译的,则会重现该问题。 如果使用xCode==6.4编译代码,则不会再现此问题 老问题是: 我正在致力于支持IOS移动应用程序。 在最近将Xcode IDE从版本6.4更新到版本7.0(支持IOS 9)之后,我遇到了一个关键问题——应用程序挂起。 使用xCode 6.4构建相

更新:我已经准备好了样本,该样本是复制没有神奇记录的问题。请使用以下URL下载测试项目:

提供的项目存在以下问题:获取时死锁 在从主线程调用的performBlockAndWait中

如果代码是使用XCode版本>6.4编译的,则会重现该问题。 如果使用xCode==6.4编译代码,则不会再现此问题

老问题是:

我正在致力于支持IOS移动应用程序。 在最近将Xcode IDE从版本6.4更新到版本7.0(支持IOS 9)之后,我遇到了一个关键问题——应用程序挂起。 使用xCode 6.4构建相同的应用程序(由相同的源代码生成)可以正常工作。 因此,如果应用程序是使用xCode>6.4构建的,则在某些情况下应用程序会挂起。 如果应用程序是使用xCode 6.4构建的,那么应用程序工作正常

我花了一些时间来研究这个问题,因此我准备了一个测试应用程序,它与我的应用程序中类似,重现了这个问题。 测试应用程序在Xcode>=7.0上挂起,但在Xcode 6.4上工作正常

测试源的下载链接:

测试应用的要求如下: 1.系统中必须安装cocoa pods manager 2.MagicalRecord 2.2版框架

测试应用程序的工作方式如下: 1.在应用程序开始时,它创建了包含10000条简单实体记录的测试数据库,并将它们保存到持久存储中。 2.在方法视图中应用程序的第一个屏幕上将出现:它运行导致死锁的测试。 使用以下算法:

-(NSArray *) entityWithId: (int) entityId inContext:(NSManagedObjectContext *)localContext 
{
   NSArray * results = [TestEntity MR_findByAttribute:@"id" withValue:[ NSNumber numberWithInt: entityId ] inContext:localContext];
  return results;
}

…..
int entityId = 88;
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = @"childContext1";

NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = @"childContext2";

NSArray *results = [self entityWithId:entityId inContext: childContext2];

for(TestEntity *d in results)
{
    NSLog(@"e from fetchRequest %@ with name = '%@'", d,  d.name); /// this line is the reason of the hangup
}

dispatch_async(dispatch_get_main_queue(), ^
               {
                   int entityId2 = 11;
                   NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"id=%d", entityId2];
                   NSArray *a = [ TestEntity MR_findAllWithPredicate: predicate2 inContext: childContext2];
                   for(TestEntity *d in a)
                   {
                       NSLog(@"e from fetchRequest %@ with name = '%@'", d,  d.name);
                   }
               });
创建了两个托管对象上下文,并发类型==NSPrivateQueueConcurrencyType(请检查Magic record framework的MR_上下文的代码)。这两个上下文都具有父上下文 并发类型=NSMainQueueConcurrencyType。从主线程开始,应用程序以同步方式执行获取(MR_findByAttribute和MR_findAllWithPredicate) 使用performBlockAndWait,其中包含获取请求)。在第一次获取之后,使用dispatch_async()在主线程上调度第二次获取

因此,应用程序挂起。似乎出现了死锁,请查看堆栈的屏幕截图:

 这是链接,我的声誉太低,无法发布图片。)

如果要注释该行
NSLog(@“e from fetchRequest%@with name='%@',d,d.name);//这一行是挂断的原因

(这是测试项目的ViewController.m中的第39行)应用程序工作正常。我认为这是因为没有读取测试实体的名称字段

因此,对于注释行 NSLog(@“e from fetchRequest%@,name='%@',d,d.name)
使用Xcode 6.4和Xcode 7.0构建的二进制文件没有挂断

使用未注释的行 NSLog(@“e from fetchRequest%@,name='%@',d,d.name)

使用Xcode 7.0构建的二进制文件存在挂起问题,而使用Xcode 6.4构建的二进制文件没有挂起问题

我认为问题的出现是因为延迟加载实体数据


有人对所描述的案例有异议吗?我将非常感谢您的帮助。

这就是为什么我不使用抽象(即隐藏)太多核心数据细节的框架。它有非常复杂的使用模式,有时您需要知道它们如何互操作的细节

首先,我对魔幻唱片一无所知,只是很多人都在使用它,所以它的功能一定很好

然而,我立即在您的示例中看到了一些对核心数据并发性的完全错误的使用,因此我查看了头文件,以了解为什么您的代码会做出这样的假设

我一点也不想责骂你,尽管乍一看这似乎是真的。我想帮助教育你们(我利用这个机会看了一眼MR

通过对MR的快速了解,我想说您对MR的功能以及核心数据的一般并发规则有一些误解

首先,你说这个

创建两个具有并发类型的托管对象上下文== NSPrivateQueueConcurrencyType(请检查 神奇记录框架)。这两个上下文都具有父上下文 并发类型=NSMainQueueConcurrencyType

这似乎不是真的。这两个新上下文确实是私有队列上下文,但它们的父上下文(根据我在github上浏览的代码)是神奇的
MR_rootSavingContext
,它本身也是一个私有队列上下文

让我们分析一下您的代码示例

NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = @"childContext1";

NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = @"childContext2";
因此,您现在有两个私有队列MOC(
childContext1
childContext2
),都是另一个匿名私有队列MOC的子级(我们将称之为
savingContext

然后对
childContext1
执行提取。那个代码实际上是

-(NSArray *) entityWithId:(int)entityId
                inContext:(NSManagedObjectContext *)localContext 
{
   NSArray * results = [TestEntity MR_findByAttribute:@"id"
                                            withValue:[NSNumber numberWithInt:entityId]
                                            inContext:localContext];
  return results;
}
现在,我们知道这个方法中的
localContext
在本例中是另一个指向
childContext2
的指针,它是一个私有队列MOC。在调用
performBlock
之外访问专用队列MOC是100%违反并发规则的。但是,由于您使用的是另一个API,并且方法名称无法帮助您了解MOC是如何访问的,因此我们需要查看该API,看看它是否隐藏了
performBlock
,以查看您是否正确访问它

不幸的是,头文件中的文档没有提供任何指示,因此我们必须查看实现。该调用最终调用了
MR_executeFetchRequest…
,文档中也没有说明它如何处理并发性。所以,我们来看看它的实现

现在,我们正在取得进展。这是f
-(NSArray *) entityWithId:(int)entityId
                inContext:(NSManagedObjectContext *)localContext 
{
   NSArray * results = [TestEntity MR_findByAttribute:@"id"
                                            withValue:[NSNumber numberWithInt:entityId]
                                            inContext:localContext];
  return results;
}
for(TestEntity *d in results)
{
    NSLog(@"e from fetchRequest %@ with name = '%@'", d,  d.name); /// this line is the reason of the hangup
}
[childContext2 performBlockAndWait:^{
    for (TestEntity *d in results) {
        NSLog(@"e from fetchRequest %@ with name = '%@'", d,  d.name);
    }
}];
dispatch_async(dispatch_get_main_queue(), ^{
    int entityId2 = 11;
    NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"id=%d", entityId2];
    NSArray *a = [TestEntity MR_findAllWithPredicate:predicate2
                                           inContext:childContext2];
    for (TestEntity *d in a) {
        NSLog(@"e from fetchRequest %@ with name = '%@'", d,  d.name);
    }
});
- (void)executeFetchRequest:(NSFetchRequest *)request
                  inContext:(NSManagedObjectContext *)context
                 completion:(void(^)(NSArray *results, NSError *error))completion
{
    [context performBlock:^{
        NSError *error = nil;
        NSArray *results = [context executeFetchRequest:request error:&error];
        if (completion) {
            completion(results, error);
        }
    }];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: testEntityDescription ];
[request setPredicate: predicate2 ];
[self executeFetchRequest:request
                inContext:childContext2
               completion:^(NSArray *results, NSError *error) {
    if (results) {
        for (TestEntity *d in results) {
            NSLog(@"++++++++++ e from fetchRequest %@ with name = '%@'", d,  d.name);
        }
    } else {
        NSLog(@"Handle this error: %@", error);
    }
}];