ios - 带有多个子ManagedObjectContexts和NSManagedObjectContextDidSaveNotification的罕见崩溃

标签 ios multithreading core-data restkit nsmanagedobjectcontext

我们从生产应用程序中获取崩溃报告。它崩溃了大约所有应用程序打开的1%,而我们在XCode session 期间或在模拟器上始终无法使应用程序崩溃。但是我们能够在没有运行XCode session 的情况下在设备上重现崩溃。我相信这是多个ManagedObjectContext之间的竞争条件,它们试图通过NSManagedObjectContextDidSaveNotification来了解有关更改的信息

但是首先这是一个示例崩溃报告:

Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x6000000c
Crashed Thread:  22

Thread 0:
0   libsystem_kernel.dylib              0x30ba24c4 semaphore_wait_trap + 8
1   libdispatch.dylib                   0x30ad17ff _dispatch_barrier_sync_f_slow + 363
2   CoreData                            0x22452ced _perform + 173
3   CoreData                            0x2245fd9f -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:] + 67
4   CoreData                            0x223e3467 _PFFaultHandlerLookupRow + 1319
5   CoreData                            0x223e2bd1 _PF_FulfillDeferredFault + 233
6   CoreData                            0x223e2a35 _sharedIMPL_pvfk_core + 61
7   myApp                               0x0014bc11 -[E5ServiceEndpointController serviceEndpointUrlForKey:withHost:andContext:] (E5ServiceEndpointController.m:116)
8   myApp                               0x0014b3bd -[E5ServiceEndpointController urlForKey:] (E5ServiceEndpointController.m:45)
9   myApp                               0x0014968b -[E5NotificationController fetchMessages] (E5NotificationController.m:117)
10  myApp                               0x0013bec1 __41-[E5MenuPointController fetchMenuPoints:]_block_invoke (E5MenuPointController.m:172)
11  myApp                               0x002af435 __66-[RKObjectRequestOperation setCompletionBlockWithSuccess:failure:]_block_invoke244 (RKObjectRequestOperation.m:474)
12  libdispatch.dylib                   0x30aca2e3 _dispatch_call_block_and_release + 11
13  libdispatch.dylib                   0x30aca2cf _dispatch_client_callout + 23
14  libdispatch.dylib                   0x30acdd2f _dispatch_main_queue_callback_4CF + 1331
15  CoreFoundation                      0x2268f619 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
16  CoreFoundation                      0x2268dd19 __CFRunLoopRun + 1513
17  CoreFoundation                      0x225db3b1 CFRunLoopRunSpecific + 477
18  CoreFoundation                      0x225db1c3 CFRunLoopRunInMode + 107
19  GraphicsServices                    0x29c08201 GSEventRunModal + 137
20  UIKit                               0x25c4543d UIApplicationMain + 1441
21  myApp                               0x00151125 main (main.m:14)
22  libdyld.dylib                       0x30aebaaf start + 3

Thread 12:
0   libsystem_kernel.dylib              0x30ba24c4 semaphore_wait_trap + 8
1   libdispatch.dylib                   0x30ad17ff _dispatch_barrier_sync_f_slow + 363
2   CoreData                            0x22452ced _perform + 173
3   CoreData                            0x2245f991 -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 241
4   CoreData                            0x223d11df -[NSManagedObjectContext executeFetchRequest:error:] + 595
5   myApp                               0x0014baf5 -[E5ServiceEndpointController serviceEndpointUrlForKey:withHost:andContext:] (E5ServiceEndpointController.m:108)
6   myApp                               0x0014b33d -[E5ServiceEndpointController pathForKey:] (E5ServiceEndpointController.m:37)
7   myApp                               0x001ac9c7 -[E5MainViewController crashMeThreadOne] (E5MainViewController.m:357)
8   Foundation                          0x233fe68b __NSThread__main__ + 1119
9   libsystem_pthread.dylib             0x30c32e23 _pthread_body + 139
10  libsystem_pthread.dylib             0x30c32d97 _pthread_start + 119
11  libsystem_pthread.dylib             0x30c30b20 thread_start + 8

Thread 22 Crashed:
0   libobjc.A.dylib                     0x3056bf46 objc_msgSend + 6
1   CoreFoundation                      0x22681e31 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 13
2   CoreFoundation                      0x225dd6cd _CFXNotificationPost + 1785
3   Foundation                          0x23333dd9 -[NSNotificationCenter postNotificationName:object:userInfo:] + 73
4   CoreData                            0x2240dbf7 -[NSManagedObjectContext(_NSInternalAdditions) _didSaveChanges] + 2303
5   CoreData                            0x223f412f -[NSManagedObjectContext save:] + 1299
6   myApp                               0x0024774f __61-[NSManagedObjectContext(RKAdditions) saveToPersistentStore:]_block_invoke16 (NSManagedObjectContext+RKAdditions.m:65)
7   CoreData                            0x2245780d developerSubmittedBlockToNSManagedObjectContextPerform + 181
8   libdispatch.dylib                   0x30aca2cf _dispatch_client_callout + 23
9   libdispatch.dylib                   0x30ad186b _dispatch_barrier_sync_f_slow + 471
10  CoreData                            0x224579a7 -[NSManagedObjectContext performBlockAndWait:] + 183
11  myApp                               0x0024720f -[NSManagedObjectContext(RKAdditions) saveToPersistentStore:] (NSManagedObjectContext+RKAdditions.m:64)
12  myApp                               0x0013d9a9 __51-[E5MenuPointController updateNotificationCounters]_block_invoke (E5MenuPointController.m:299)
13  Foundation                          0x233e8db1 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 9
14  Foundation                          0x23353e4d -[NSBlockOperation main] + 149
15  Foundation                          0x233467c7 -[__NSOperationInternal _start:] + 775
16  Foundation                          0x233eb71b __NSOQSchedule_f + 187
17  libdispatch.dylib                   0x30ad2729 _dispatch_queue_drain + 1469
18  libdispatch.dylib                   0x30accaad _dispatch_queue_invoke + 85
19  libdispatch.dylib                   0x30ad3f9f _dispatch_root_queue_drain + 395
20  libdispatch.dylib                   0x30ad53c3 _dispatch_worker_thread3 + 95
21  libsystem_pthread.dylib             0x30c30dc1 _pthread_wqthread + 669
22  libsystem_pthread.dylib             0x30c30b14 start_wqthread + 8

所有崩溃报告的共同点是都涉及__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__,并且其他一些线程尝试与它们自己的ManagedObjectContext(MOC)一起工作,例如同时执行获取操作或更改托管对象上的数据。

我们的应用程序使用RestKit 0.24进行CoreData管理和子MOC创建。我们使用RestKit方法-(NSManagedObjectContext*)newChildManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType tracksChanges:(BOOL)tracksChanges为每个线程创建一个新的子MOC。

此方法非常简单,可以通过here on gitHub查看

在该gitHub代码中,您甚至可以看到tracksChanges使用以下代码[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleManagedObjectContextDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:observedContext];添加了一个观察者,还删除了dealloc中的观察者

我们的发现是,如果将tracksChanges设置为NO,则不会发生崩溃。如果将tracksChanges设置为YES,我们可以重现崩溃。请记住,崩溃并非每次都会发生。这非常罕见,我们更改了代码以不断地重新运行有问题的代码段,从而有机会重现崩溃。

这是E5ServiceEndpointController类的代码段,如果将tracksChanges设置为YES,则该代码段可能会导致崩溃:
- (NSString *)urlForKey:(NSString *)key
{
    NSManagedObjectContext *serviceEndpointContext = [self newChildManagedObjectContextForServiceEndpoints];
    return [self serviceEndpointUrlForKey:key withHost:YES andContext:serviceEndpointContext];
}

- (NSString *)pathForKey:(NSString *)key
{
    NSManagedObjectContext *serviceEndpointContext = [self newChildManagedObjectContextForServiceEndpoints];
    return [self serviceEndpointUrlForKey:key withHost:NO andContext:serviceEndpointContext];
}

-(NSManagedObjectContext *)newChildManagedObjectContextForServiceEndpoints{
    return [[[E5RestKitManager sharedInstance] managedObjectStore] newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType tracksChanges:YES];
}

- (NSString *)serviceEndpointUrlForKey:(NSString *)key withHost:(BOOL)includeHost andContext:(NSManagedObjectContext *)context
{
    NSFetchRequest *serviceEndpointFetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"ServiceEndpoint"];
    [serviceEndpointFetchRequest setPredicate:[NSPredicate predicateWithFormat:@"key = %@", key]];

    NSArray *result = [context executeFetchRequest:serviceEndpointFetchRequest error:nil];

    // more code here

}

我们在这里想念什么?我们需要用什么保护executeFetchRequest吗?如果我们检测到NSManagedObjectContextDidSaveNotification,是否需要手动更新子MOC或放弃所有操作?我们在架构上有误解吗?

最佳答案

看起来很简单,没有遵循队列限制规则:

  • urlForKeypathForKey调用newChildManagedObjectContextForServiceEndpoints以获取新的托管对象上下文
  • newChildManagedObjectContextForServiceEndpoints使用NSPrivateQueueConcurrencyType创建此上下文。
  • urlForKeypathForKey将其上下文传递给serviceEndpointUrlForKey:andContext:
  • serviceEndpointUrlForKey:andContext:执行以下操作:
    NSArray *result = [context executeFetchRequest:serviceEndpointFetchRequest error:nil];
    

  • 规则是:如果使用队列限制(此处为NSPrivateQueueConcurrencyType)创建托管对象上下文,则必须在使用那个上下文时使用performBlock:performBlockAndWait:。否则,您将绕过队列限制应该提供的并发支持。您需要在使用这些上下文之一的任何地方修复此问题。您还应该研究使用com.apple.CoreData.ConcurrencyDebug查找与并发相关的错误。

    关于ios - 带有多个子ManagedObjectContexts和NSManagedObjectContextDidSaveNotification的罕见崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29540135/

    相关文章:

    ios - 导入什么以在 iOS 中使用 Facebook Graph API?

    C++11 线程与异步性能(VS2013)

    iphone - 在 iPhone 上使用 OpenGL 置换贴图

    c++ - 从 STL 容器中并行读取

    java - volatile 的目的/优点

    ios - 为 CoreData 设置默认值

    ios - 使用 Group By 名称获取核心数据属性

    ios - 在 swift 中解开一个可选值,这是一个可选值,但 swift 不知道它是一个可选值

    ios - Mulipartpost NSURLConnection over 3G 网络问题

    ios - 自定义选项卡组加速器