Core Data doesn't maintain relationship inverses mid-undo

I was experimenting recently with implementing a doubly linked list in Core Data as a means to store ordered relationships and discovered a rather gnarly problem. It's summarised in the title of this post, but what does that actually mean?

Let's imagine you've got an entity with a one-to-one relationship to itself.


So you can easily construct a chain of Foos, and move from one to another using -nextFoo and -previousFoo. When calling -setNextFoo: or -setPreviousFoo: Core Data very kindly maintains the other side of the relationship for you.

The problem comes while undoing or redoing a change to the relationship and you are observing the relationship, perhaps for display in the UI. Let's say you're redoing an operation that connects two Foos together. The order of operations will be something like this:

[foo1 setPrimitiveNextFoo:foo2];
[foo1 didChangeValueForKey:@"nextFoo"];
[foo2 willChangeValueForKey:@"previousFoo"];
[foo2 setPrimitivePreviousFoo:foo1];
[foo2 didChangeValueForKey:@"previousFoo"];

The issue here is that anything observing the nextFoo property of foo1 will be notified of the change before foo2's previousFoo has been updated to match. The only way to know when the relationship is back in a consistent state is to then observe foo2's previousFoo too.

If only the Core Data team had made life more pleasant by doing something more like this:

[foo1 willChangeValueForKey:@"nextFoo"];
[foo2 willChangeValueForKey:@"previousFoo"];
[foo1 setPrimitiveNextFoo:foo2];
[foo2 setPrimitivePreviousFoo:foo1];
[foo1 didChangeValueForKey:@"nextFoo"];
[foo2 didChangeValueForKey:@"previousFoo"];

Then any object observing either property will only be notified once the change is complete. Oh well.

Can I bind one object to another and have the binding use to-many property accessor methods?

Short answer: No.

Long answer:

As well as being great for UI code, Key-Value Bindings can be terrifically helpful for just tying two objects together deeper down at the controller/model layer. I use this technique quite a bit in Sandvox, but the other day wondered if I could push it further.

Let's imagine you've got two classes declared like this:

@interface ModelObject : NSObject
@property(copy) NSSet *foos;
- (NSMutableSet *)mutableFoos; // will send correct KVO-notifications
@end

@interface ControllerObject : NSObject
- (NSSet *)bars;
- (void)addBarsObject:(id)object;
- (void)removeBarsObject:(id)object;
@end

Wouldn't it be great if you could bind bars to foos? Well you can't. Key-Value Bindings don't stretch so far as to use your -addBarsObject: and -removeBarsObject: methods. Instead, it will just call [controller setValue:foos forKeyPath:@"bars"], completely ignoring the to-many accessors!

So having found this didn't work, I resigned myself to manually setting up KVO to handle the grunt work. Or did I?

Medium answer:

What?! Are you crazy? Why would you want to do this? Apple's provided a perfectly decent NSArrayController for any binding involving a to-many relationship. Use that you numpty!

Oh. Er, yeah. It seems all too often I'm trying to bypass the use of an array controller because I don't require all of its features, yet I would be far better just starting with it and ignoring that which isn't needed in the particular case.

The rules of Inspector Club

  1. The first rule of Inspector Club is you do not talk about Inspector Club
  2. Oh, alright then, you can talk about it if you like
  3. To show or hide the Inspector, provide an item in the View menu

    Matching toolbar item and keyboard shortcut are a given. Command-I, Command-Option-I, or Command-Shift-I all seem reasonable choices, although Apple favour Command-Option-I in their iWork apps.

    Some prefer placing the controls in the Window menu rather than View. I can see the sense in either, but on the whole Apple seem to feel that the View menu is best, leaving the Window menu for generic window commands only.

  4. The Inspector is an NSPanel

    You can easily set all properties up in Interface Builder (-isFloatingPanel etc.), except oddly [panel setBecomesKeyOnlyIfNeeded:YES], which must be done programmatically.

  5. Use small-size controls and text

    There may be occasions where you need to stray to mini-size controls, but it is very much the exception.

  6. Use a margin of 10 pixels

    On the whole, IB's guides will set you right (6 or 8 pixel gaps between most things), but for the left, right & bottom margins use a spacing of 10 pixels. (Mac OS X generally uses a margin of 20 pixels in windows, so halving it for such a compact design makes sense I guess.)

  7. For all but the very simplest Inspector, split into tabs across the top

    Frustratingly, there's no built-in control for this. NSMatrix offers the right behaviour (at least for iWork/iWeb – Interface Builder does its own thing), but you'll need some custom drawing code. Hopefully I'll be able to open source some eventually.

  8. If you need to cram in more than would fit within a comfortable panel height, further split with an NSTabView

    Place 10 pixels below the tabs (as with all measurements in this guide, this is the gap reported by Interface Builder if you hold down the Option key). Note how all but the top edge of the Tab View is placed offscreen so as to preserve space/cleanliness.

  9. Divide into sections

    Use NSBox set to be a horizontal line. Generally dividers should be 10 pixels below the content of the previous section, but quite often you'll want to bump this up a bit to stop the design feeling too cluttered.

    Be careful how many sections you use. Too many and you just increase clutter; too few and a section can become overwhelming. Your own judgement becomes paramount! (Don't worry, you should have plenty of time to tweak the design as it sinks in). Of course it's often quite acceptable to have only a single section, and if so you don't need to layout any dividing lines – very clean.

  10. Give each section a title

    Each section gets its own title (and hey, even Apple can make mistakes – in the screenshot above, that title's baseline is 1 pixel above the popup button's!). Use the small, bold system font.

    The title always goes at the top left of the section. Don't be tempted to nudge it right so that it aligns with another control; you'll spoil the readability.

    Working the title into the design is a rather creative exercise. Often it's best to simply have it stand alone with all other section content underneath:

    But at other times, you bring the content up to the same level, and can even make something like a checkbox the title:

    As with all good rules, this one's made to be bent. Some sections are so simple they need no title, or another element fills the role already:

    And on other occasions you may go even further (consult your designer first):

  11. Consider supporting multiple Inspectors open at once

    If you're up to the challenge, also provide a "New Inspector" command (in the View menu, just below Show/Hide Inspector is a good spot for this). All open Inspectors still follow the selection, but can be displaying different tabs, sometimes making the life of your app's power users a little better.

    For example, I used to do this with Pages so that I could see the document's word count while still using another Inspector on different tabs.

    This does mean that your menu item potentially has four states:

    • Show Inspector
    • Hide Inspector
    • Show Inspectors
    • Hide Inspectors

    -validateMenuItem: is a great place for handling this.

© Mike Abdullah 2007-2012