Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infrequent CoreData crash "nilOutReservedCurrentEventSnapshot"

There is a crash occurring within my app that only happens quite rarely (maybe once every 30 runs). The error code contains a strange selector name _nilOutReservedCurrentEventSnapshot__ which I haven't been able to find any documentation for at all. Here is the feed from my console:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFType _nilOutReservedCurrentEventSnapshot__]: unrecognized selector sent to instance 0x157b51e0'
*** First throw call stack:
(0x2358810b 0x22d2ee17 0x2358d925 0x2358b559 0x234bbc08 0x24cbf445 0x24ca4d99 0x249bec 0x245c90 0x19b68c 0x24a5c97 0x24b05ab 0x24a8ef9 0x24b1a8d 0x24b18e7 0x232bfb29 0x232bf718)
libc++abi.dylib: terminating with uncaught exception of type NSException

If anyone is able to shed some light on the meaning of this phrase _nilOutReservedCurrentEventSnapshot__`, that would help me immensely. A screenshot of the location of the crash is below:

The error

like image 972
PJeremyMalouf Avatar asked Mar 01 '16 07:03

PJeremyMalouf


2 Answers

Unfortunately there isn’t much you can find about _nilOutReservedCurrentEventSnapshot__ online..

It is probably related to the snapshot lifecycle of a managed object.

When Core Data fetches an object from a persistent store, it takes a snapshot of its state. A snapshot is a dictionary of an object’s persistent properties—typically all its attributes and the global IDs of any objects to which it has a to-one relationship. Snapshots participate in optimistic locking. When the framework saves, it compares the values in each edited object’s snapshot with the then-current corresponding values in the persistent store.

The fact that _nilOutReservedCurrentEventSnapshot__ is not documented means that this type of behaviour is not supposed to happen.

What we do know is that it is a function of the NSManagedObject class.

Therefore The error unrecognized selector sent to instance was caused because _nilOutReservedCurrentEventSnapshot__ was called on an object that was not an NSManagedObject, because the NSManagedObject was deallocated and something else now fills its memory. This is fact.

Context isn’t given from the question about the nature of the app and its CoreData setup but it is inferred that it is using a parent-child concurrency pattern. This is important.

Parent-Child context pattern
[Image retrieved from here]

From all the questions asked on stack overflow that I can find about this error, it appears they all use the parent-child concurrency pattern.

It is entirely possible that the issue is caused by the adoption of this pattern with improper implementation or handling of ManagedObjects.

A Situation where a parent-child context may be used is when syncing cloud data or handling changes made when a user edits something with the option of discarding or undoing the changes made, which can be done on a background thread.

It is also mentioned in the question that this is a rare occurrence and doesn’t happen every time. Meaning that the contexts are probably fine until a certain save or change is made and somehow the contexts become out of sync, and performing another save crashes the app.

From the docs on Synchronising changes between contexts:

If you use more than one managed object context in an application, Core Data does not automatically notify one context of changes made to objects in another. In general, this is because a context is intended to be a scratch pad where you can make changes to objects in isolation, and you can discard the changes without affecting other contexts.

Changes will be sent to the parent context when the child is saved but not if the parent has been changed separately. It is strongly adviced you never change the parent context; only through saving the child context and propagating the changes from there.

Possible reason for crash



There are several things that may cause this but two that stick out are:

1. deallocation of ManagedObject from reference that is disposed of due to dismissing a modal view in an iOS application or sheet in an OSX application.

possible solution: set the retainsRegisteredObjects property of the child context to true before fetching ManagedObjects (and then setting to false once the contexts have been saved to avoid further potential memory leaks). warning!

eg. ctx.retainsRegisteredObjects = true



2. Unhandled changes to a context.

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/ChangeManagement.html#//apple_ref/doc/uid/TP40001075-CH22-SW1

Consider an application with two managed object contexts and a single persistent store coordinator. If a user deletes an object in the first context (moc1), you may need to inform the second context (moc2) that an object has been deleted. In all cases, moc1 automatically posts an NSManagedObjectContextDidSaveNotification notification via the NSNotificationCenter that your application should register for and use as the trigger for whatever actions it needs to take. This notification contains information not only about deleted objects, but also about changed objects. You need to handle these changes because they may be the result of the delete. Most of these types of changes involve transient relationships or fetched properties.

Possible solutions here are:

  • Use block methods on your contexts. refer to callback function at bottom of linked file.

  • Make sure to use objectWithID when passing around ManagedObjects between contexts.

  • Taking appropriate action on object change using a NSManagedObjectContextDidSaveNotification trigger.

  • removing all strong references to a ManagedObject. It may be possible that a strong reference was made to a ManagedObject used outside of the scope of the given code and so when the parent deallocated the ManagedObject and the child didn't, on a future save the child performed another save on that ManagedObject and then the parent context tried saving the ManagedObject which was deallocated. "it is a good pattern to try to fetch objects, modify them, save them and reset the edit [child] context"



Because there is no other information here to deduce what is causing the error, and I can’t reproduce it.. I would suggest trying to find where your ManagedObject is deallocated using Zombie profiling in Instruments.

If a message is then sent to one of these deallocated objects (which are now NSZombie objects), the zombie is flagged, the app crashes, recording stops, and a Zombie Messaged dialog appears. You can then examine the retain and release history of the zombie object to determine exactly where the problem occurred.

https://github.com/iascchen/SwiftCoreDataSimpleDemo/blob/master/SwiftCoreDataSimpleDemo/CoreDataHelper.swift

Hopefully then someone can shed more light on what caused the issue.



+++++++++++++++++++++++++++++++++

Side Note:
parent/child contexts could result in stuttering from the UI - “every fetch request and every save operation will fully block the main thread while data is read or written to/from disk”. Which is because every fetch is pulled through the parent context (on the main thread) to the child.

supporting SO answer

It is recommended that if you are using this approach that you rethink the design to something like two independent managed object contexts connected to the same persistent store coordinator Which might 'avoid' the issue at hand in future from happening.

Two contexts one coordinator [Image retrieved from here]

You can see the drastic performance differences in the linked article

like image 110
Danoram Avatar answered Oct 20 '22 07:10

Danoram


_nilOutReservedCurrentEventSnapshot__ is a private method on NSManagedObject internally to flush attributes and values from a Core Data object. You can see it in the private header for NSManagedObject here.

__NSCFType is an private wrapper for Core Foundation types used internally by the Objective-C runtime. You can learn more by looking at the private header here, but there isn't much to see.

Without the full backtrace, it's hard to debug the specific issue. The way I see it, there could be two culprits:

  1. The parentObject of your context is somehow invalid.

  2. You're trying to save a Core Foundation object as a property on an NSManagedObject which isn't expecting it.

like image 28
JAL Avatar answered Oct 20 '22 07:10

JAL