We have a few apps in the store that are working fine on iOS 15, but on iOS 16, have started exhibiting some strange crashes in Core Data. Specifically, we're seeing this happen at very different parts of the app:
CoreData: error: warning snapshot_get_value_as_object called on NULL
The top of the stack traces all appear fairly consistent, though sometimes all of the symbols on the stack are framework code, not our app:
#0 0x000000019447ba50 in snapshot_get_value_as_object ()
#1 0x00000001944ab724 in _PFFaultHandlerFulfillFault ()
#2 0x0000000194479798 in _PFFaultHandlerLookupRow ()
#3 0x00000001944a0cf4 in _PF_FulfillDeferredFault ()
As far as I can tell, there aren't any documented changes to Core Data in iOS 16 aside from CloudKit functionality, though there could certainly be internal changes in the framework itself.
I'm at something of a loss regarding how to try to isolate the cause of this issue and figure out how to fix it.
After throwing just about everything I could think of at the wall, I did end up making a change that seems to have totally stopped the crashing.
For the record, I have a thread going with DTS that seems skeptical of that change fixing anything, so please take this with a grain of salt. They're saying that it's likely to be the result of either concurrency violation or memory corruption, and recommending debugging the app with both the -com.apple.CoreData.ConcurrencyDebug 1
app argument and Xcode's Guard Malloc turned on.
That disclaimer aside, here's what happened with our app. We have a particular managed object subclass in our app with a couple of transient properties whose value are derived from another (persistent) property. In order to ensure that the transient values are reset when changes are merged from other contexts (often an update from our API), that subclass overrode -awakeFromSnapshotEvents:
like so:
- (void)awakeFromSnapshotEvents:(NSSnapshotEventType)flags
{
[super awakeFromSnapshotEvents:flags];
[self setPrimitiveFoo:nil];
[self setPrimitiveBar:nil];
}
I was able to get a semi-reproducible case where I observed that merging the changes from the other context reset the affected objects, turning them back into faults (and resetting the transient properties). I added a basic conditional to that method to check whether the receiver was already a fault before clearing the transient properties' values.
- (void)awakeFromSnapshotEvents:(NSSnapshotEventType)flags
{
[super awakeFromSnapshotEvents:flags];
if( !self.isFault ) {
[self setPrimitiveFoo:nil];
[self setPrimitiveBar:nil];
}
}
I still don't fully understand why this change fixed anything, but we haven't seen the app crash trying to get a snapshot since. I confess I haven't had time yet to go back to the previous state and try running with Guard Malloc enabled to see whether that would shed more light on the issue.