Utilizing undo manager in parent managed object context to batch changes in child leads to validation errors with empty/nilled objects.

Number:rdar://FB7684093 Date Originated:05/01/2020
Status:Open Resolved:
Product:Core Data Product Version:iOS 13.3
Classification:Application Crash Reproducible:
Utilizing undo manager in parent managed object context to batch changes in child leads to validation errors with empty/nilled objects.

In our application, because changes are made to multiple objects in real time based on user typing, for undo we need to batch changes in a managed object context, rather than utilize event-loop based undo, which would undo one “letter” at a time.

In our app we’re attempting to utilize the batching property of child object contexts’ saves for undo rather than undo groups, where we have found bugs that are hard to reproduce and debug.

Under this scheme, the main managed object context hosts an undo manager, and is saved before and after the child managed object context is saved when changes are made. An UndoManager subclass ensures that after the main context is "undone" or "redone", a main context save propagates the changes to the child object context.

However, we’re having issues with nil-objects appearing after undoing inserts. These nil objects cause database validation errors.

The sample application we’re providing (based on the template split view application) has a main managed object context (`persistentContainer.viewContext`) and an auto-merging child context used in the `MasterViewController`.

Current status for resolving bug 1 (steps to reproduce section): I have considered deleting empty objects when they fail validation in the parent object context, but believe this will not allow redo. Instead, we reverted to moving the undo manager to the child context, where we manage undo groups.

Steps to reproduce:
Bug 1:
Run the provided demo application
1. Press insert
2. Press undo
3. Press insert
4. Press undo

Expectation: Undone insert
Result: Validation error about saving an object with nil properties. That is, a totally nil object is registered as inserted in the child object context. 

Try going to the xcdatamodeld and making timestamp an optional property, repeating the above procedure: instead of a crash, the nil object appears in the child context. Repeating steps 3 & 4 inserts additional nil objects. From experience in our app, all present properties of the object are nil.

I have already figured out the solution to bug #2, which is included only because it may be related.

Bug 2: See `insertNewObject()` in MasterViewController, where `obtainPermanentIDs` is needed to prevent duplicate objects from appearing in fetched results controller of the child MOC.
1. Comment out the relevant lines in insertNewObject, run the application.
2. Press insert

Expectation: One object inserted.
Result: Two objects inserted.

iOS 13.3 simulator and real hardware (iPhone X). Targets 13.2 & 11.0. Swift versions 5 & 4.2. XCode 11.3.1.


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!