2

My app's main function is to arrange objects on a plot. The whole thing is based on Core Data, and each object on the plot is represented by an entity. For this example, that entity will be "Unit," a custom subclass of NSManagedObject.

When the plot is opened, each Unit gets a UnitViewController created, which KVO-observes changes to the Unit's properties, such as .positionX, .positionY, and .angle. Touch gestures that the UnitViewController detect modify the Unit object, and the view responds by moving itself when it observes them. This allows an easy implementation of Undo and Redo without much extra code, where the Units move themselves around when the database changes.

The problem arises when the Unit is deleted.

One property that the Unit has is .viewController, which points to the UnitViewController that's linked to it. It's a transient property, because obviously I don't need it to persist; I re-create it every time I open the plot. (The UnitViewController has a property .object, which points to its Unit; essentially an inverse property.) The problem is, when I Undo a delete, or Redo the Undo of a creation, Core Data tries to restore the .viewController property, but that UnitViewController object has been deallocated. I was getting an EXC_BAD_ACCESS until I enabled zombies, which gave me this:

*** -[UnitViewController retain]: message sent to deallocated instance 0x18365780

So my question is: how can I get the NSUndoManager to completely ignore that property? I've tried turning off undo registration before modifying it, even to the point of overriding the setter method in Unit+LD.m (my category on Unit.m, which is the NSManagedObject subclass):

- (void)setViewController:(UnitViewController *)viewController
{
    [self.managedObjectContext.undoManager disableUndoRegistration];

    [self willChangeValueForKey:@"viewController"];
    [self setPrimitiveValue:viewController forKey:@"viewController"];
    [self didChangeValueForKey:@"viewController"];

    [self.managedObjectContext.undoManager enableUndoRegistration];
}

But still, it tries to restore the .viewController when I undo a delete. Anyone dealt with anything like this before? Is there a better way to go about this?

  • 1
    Giving your managed objects references to a view controller is pretty weird, why is this necessary? Normally the view controller would hold references to the model objects it's dealing with, but reverse connections are extremely unusual. – Tom Harrington May 02 '14 at 17:20
  • It seemed a bit weird to me too, but it allows me to send messages to the view controllers very quickly when I perform certain actions. The view controllers all interact with each other a lot, and by using the .viewController property, I enable them to talk to each other very efficiently. I also can perform operations based on Core Data fetched results that return sets of managed objects, rather than enumerating through all the childViewControllers of my workspace and checking them for certain criteria. – Clayton Combe May 02 '14 at 18:02
  • Still, the disadvantages clearly outweigh the benefits. You can keep a simple collection (e.g. dictionary) with pointers to the objects and view controllers that are active. This should not be much more complicated than your solution which is problematic. – Mundi May 02 '14 at 22:31
  • Unfortunately, it is more complicated. I wrestled early on with the idea, and the code I was writing was becoming expensive quickly. It's not complicated to enumerate through the childViewControllers of my PlotViewController and see if their .object property matches the managed object I'm looking for, but four or five nested enumerations began to take their toll on performance with even the simple plots. Of course, you may be entirely right that there's no way around the problem otherwise, but I figured it couldn't hurt to see if anyone had a solution before re-writing half my code! – Clayton Combe May 04 '14 at 10:43

1 Answers1

1

I ended up submitting a support ticket to Apple, and they came back immediately with a pretty simple answer: override the setter method -setViewController: in Unit.m and make it empty. (Or in Unit+LD.m, in my case, since I do all my custom stuff in a category to avoid having to re-do it when I update my data model.)

- (void)setViewController:(UnitViewController *)viewController {

}

I only set the property a couple times in the code, and when I do, I just use -setPrimitiveValue:forKey: instead. I still have some testing to do to make sure everything else works, but for now, it seems to work like a charm. The undo manager doesn't ever try to restore the property and I've stopped getting crashes.