我们从生产应用程序返回崩溃报告。它崩溃了大约1%的应用程序开放,我们无法让应用程序在XCode会话期间或模拟器上崩溃。但是我们能够在没有运行XCode会话的设备上重现崩溃。我认为这是多个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或丢弃所有操作?我们是否存在架构上的误解?
答案 0 :(得分:3)
看起来像是一个不遵循队列限制规则的简单案例:
urlForKey
和pathForKey
调用newChildManagedObjectContextForServiceEndpoints
以获取新的托管对象上下文newChildManagedObjectContextForServiceEndpoints
使用NSPrivateQueueConcurrencyType
创建此上下文。urlForKey
和pathForKey
将其上下文传递给serviceEndpointUrlForKey:andContext:
serviceEndpointUrlForKey:andContext:
执行此操作:
NSArray *result = [context executeFetchRequest:serviceEndpointFetchRequest error:nil];
规则是:如果使用队列限制创建托管对象上下文(此处为NSPrivateQueueConcurrencyType
),则必须使用performBlock:
或performBlockAndWait:
上下文。如果你不这样做,你就会绕过队列限制应该提供的并发支持。您需要在使用其中一个上下文的任何地方修复它。您还应该考虑使用com.apple.CoreData.ConcurrencyDebug
来查找与并发相关的错误。