I use Core Data in my iOS project.
I am using multiple contexts in the following way. I have a persisent store context
that operates on a private queue and stores changes to the persistent store.
I have a main queue context
that is the child of persistent store context
. All of the FRC
s in my app use this context.
And finally, if I have to do some changes that I want to save in a batch, I create and use new NSManagedObjectContext
s that are children of the main queue context.
So I have a chain:
DB < persistent store context < main queue context < any other child contexts
I have a singleton that retains both persistent store context
and main queue context
. This singleton also listens to NSManagedObjectContextObjectsDidChangeNotification
notification on persistent store context
and reacts to the notification like this:
-(void) persistentStoreContextDidChangeNotification:(NSNotification*)notification
{
if (notification.object == self.persistentStoreContext) {
[self.persistentStoreContext performBlockAndWait:^{
//on every change on persistentStoreContext, save those changes using background thread to real persistent store
NSError *error = nil;
[self.persistentStoreContext save:&error];
}];
}
}
So, when I call [mainQueueContext save]
, this is enough for all changes in mainQueueContext
to be persisted in the filesystem.
Using Crashlytics, I am getting a lot of crash reports like this:
Fatal Exception: NSInternalInconsistencyException
Can't create externalDataReference interim file : 28
Thread : Fatal Exception: NSInternalInconsistencyException
0 CoreFoundation 0x0000000183f51e48 __exceptionPreprocess + 132
1 libobjc.A.dylib 0x000000019464c0e4 objc_exception_throw + 60
2 CoreData 0x0000000183c6e5b4 +[_PFRoutines writePFExternalReferenceDataToInterimFile:] + 960
3 CoreData 0x0000000183ceaa4c -[NSSQLCore writeExternalDataReferences] + 224
4 CoreData 0x0000000183c429fc -[NSSQLCore saveChanges:] + 596
5 CoreData 0x0000000183c0b078 -[NSSQLCore executeRequest:withContext:error:] + 720
6 CoreData 0x0000000183cd2254 __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 4052
7 CoreData 0x0000000183cd9654 gutsOfBlockToNSPersistentStoreCoordinatorPerform + 180
8 libdispatch.dylib 0x0000000194c9136c _dispatch_client_callout + 16
9 libdispatch.dylib 0x0000000194c9a6e8 _dispatch_barrier_sync_f_invoke + 76
10 CoreData 0x0000000183ccccb4 _perform + 180
11 CoreData 0x0000000183c0ac34 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 300
12 CoreData 0x0000000183c31400 -[NSManagedObjectContext save:] + 1284
13 MyApp 0x00000001000d17e0 __58-[XEECDStack persistentStoreContextDidChangeNotification:]_block_invoke (XEECDStack.m:426)
14 CoreData 0x0000000183ca5270 developerSubmittedBlockToNSManagedObjectContextPerform + 200
15 CoreData 0x0000000183ca5474 -[NSManagedObjectContext performBlockAndWait:] + 232
16 MyApp 0x00000001000d1774 -[XEECDStack persistentStoreContextDidChangeNotification:] (XEECDStack.m:423)
17 CoreFoundation 0x0000000183ef81e0 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
18 CoreFoundation 0x0000000183e37370 _CFXNotificationPost + 2060
19 Foundation 0x0000000184d32cc0 -[NSNotificationCenter postNotificationName:object:userInfo:] + 72
20 CoreData 0x0000000183c33d14 -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] + 364
21 CoreData 0x0000000183c321bc -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 2552
22 CoreData 0x0000000183cadba4 -[NSManagedObjectContext(_NestedContextSupport) _parentProcessSaveRequest:inContext:error:] + 1568
23 CoreData 0x0000000183cae684 __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke + 600
24 CoreData 0x0000000183cb0398 internalBlockToNSManagedObjectContextPerform + 108
25 libdispatch.dylib 0x0000000194c9136c _dispatch_client_callout + 16
26 libdispatch.dylib 0x0000000194c9a6e8 _dispatch_barrier_sync_f_invoke + 76
27 CoreData 0x0000000183ca06cc _perform + 208
28 CoreData 0x0000000183cae354 -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 176
29 CoreData 0x0000000183c31400 -[NSManagedObjectContext save:] + 1284
30 MyApp 0x000000010012ea14 __78-[MyAppManager downloadMediaIfNeededForMyAppWithManagedObjectID:onCompletion:]_block_invoke (MyAppManager.m:415)
31 MyApp 0x00000001001ce06c __38-[FLNMyAppMediaDownloadOperation main]_block_invoke60 (FLNMyAppMediaDownloadOperation.m:84)
32 Foundation 0x0000000184e07508 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 16
33 Foundation 0x0000000184d58c94 -[NSBlockOperation main] + 96
34 Foundation 0x0000000184d4861c -[__NSOperationInternal _start:] + 636
35 Foundation 0x0000000184e0a26c __NSOQSchedule_f + 228
36 libdispatch.dylib 0x0000000194c9136c _dispatch_client_callout + 16
37 libdispatch.dylib 0x0000000194c95980 _dispatch_main_queue_callback_4CF + 932
38 CoreFoundation 0x0000000183f096a0 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
39 CoreFoundation 0x0000000183f07748 __CFRunLoopRun + 1492
40 CoreFoundation 0x0000000183e351f4 CFRunLoopRunSpecific + 396
41 GraphicsServices 0x000000018cfcb5a4 GSEventRunModal + 168
42 UIKit 0x0000000188766784 UIApplicationMain + 1488
43 MyApp 0x0000000100119cc8 main (main.m:17)
44 libdyld.dylib 0x0000000194cbaa08 start + 4
So, in this specific crash, the line
30 MyApp 0x000000010012ea14 __78-[MyAppManager downloadMediaIfNeededForMyAppWithManagedObjectID:onCompletion:]_block_invoke (MyAppManager.m:415)
is calling [mainQueueContext save]
.
The same crash occurs in situations where the [mainQueueContext save]
is called from different parts of the app.
I am trying to find out what are the possible reasons why I am seeing this exception. And I cannot find anything on Google when searching for "Can't create externalDataReference interim file"
EDIT
I have went through specific crash instances and looked at them one by one. Although the stack trace of every crash is fairly similar, it turns out that the exception differs a little bit between instances.
The differents 'versions' of this crash are:
Fatal Exception: NSInternalInconsistencyException
External data reference can't find underlying file.
,
Fatal Exception: NSInternalInconsistencyException
Missing bytes from file at path /private/var/mobile/Containers/Data/Application/E9D51467-7941-41B8-88EE-31A70A82BC40/tmp/.LINKS/BD171390-D95C-459E-96D6-462318016138/EDB75A4F-C8FD-4BC4-ABD1-BA408F3A7DC9_0x18038c80, expected 276428, got 4294967295
,
Fatal Exception: NSInvalidArgumentException
Unable to open file with path: /var/mobile/Containers/Data/Application/19E53BC4-7891-4B0C-9454-27C1A0DAB2A0/Documents/persistent-store/.Model_SUPPORT/_EXTERNAL_DATA/CA6BCE7D-0C74-41CF-8784-8EB1F66DFF4C (13)
,
Fatal Exception: NSInternalInconsistencyException
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
Also, I have discovered a pattern shared by almost all the crashes of this type. The available disk space on the device is extremely low. On iPhone 4 and iPhone 5 devices the available disk space is 0%. And on iPhone 6, it's always 8%.
I suspect that the iPhone 6 reserves 8% of disk space "for special occasions".
I have managed to reproduce this issue by filling up entire disk space on the device and then using the app, storing media in Core Data.
After some time using the app, a warning popup shows that the device is low on disk space. I dismiss the warning and continue to use the app. After some additional time, the app crashes with the message:
I use a similar process in my app, with two contexts set during the construction of my core data stack and using:
NSPrivateQueueConcurrencyType
,NSMainQueueConcurrencyType
.To take a guess, the blocks may be conflicting with the operation of the notification, however that is a guess.
For my app I've modified the solution presented by Marcus Zarra in his book from The Pragmatic Bookshelf – "Core Data, 2nd Edition, Data Storage and Management for iOS, OS X, and iCloud" (Jan 2013).
For a more up to date and all encompassing solution that I have not yet implemented, read this article My Core Data Stack by Zarra.
My modified solution involves writing a custom save method built alongside (in the same class as) my core data stack, shown following, instead of using a
NSManagedObjectContextObjectsDidChangeNotification
. Note that the save method is called at appropriate points in code.Maybe this is a suitable alternative?
Properties...
Custom save method...