5

I'm reading a book on Objective-c and learning about the undo manager. The concept seems very simple but the provided example seems overly complex. Basically, I have a table view connected to an NSArrayController and I add or remove people to an array and I can edit their names and stuff. Because the example uses NSArrayController and bindings, add and remove are automatic and all of the editing is automatic.

To use the undo manager, from what I understand, I need to implement my own methods to add/remove/edit.

These methods I've implemented to do the adding and removing and get called automatically due to key value coding:

- (void)removeObjectFromEmployeesAtIndex:(int)index;
- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index;

Then for editing, I had to register the class as an observer and observe changes to edit:

- (void)changeKeyPath:(NSString *)keyPath
             ofObject:(id)obj
              toValue:(id)newValue

Here are my questions:

  1. Why do I have to do so much? My understanding was that using the NSArrayController and bindings was supposed to make things like adding/removing/editing items easier and more automatic. But if I have to implement all of these methods manually anyway just to add undo support, why use NSArrayController or bindings at all?

  2. What's going on behind the scenes? In Interface Builder, the add button is connected to the add method on the NSArrayController. How then does my insertObject method get called? I know it's through key value coding but what makes the NSArrayController's add method get overridden just b/c my document implements this method?

  3. The solution is asymmetric. I use one concept to handle undoing add/remove and another concept to handle undoing edits. Couldn't I also just observe changes to the array? I suppose it would complicate the observeValueForKeyPath method, but would that make more sense?

JPC
  • 8,096
  • 22
  • 77
  • 110
  • Are you using Core Data? – Chris Frederick Jun 12 '11 at 18:35
  • No, I haven't learned about that yet – JPC Jun 12 '11 at 22:59
  • 1
    you're saving pain by learning this before Core Data; it'll make a lot more sense then. A bit of advice I can offer is that you will find things in Cocoa/Xcode that are very convenient to use but often you have to work around. A specific example is using IB to create GUIs - doing, complex things it can become useless (for animations and so on) and you have to get behind the scenes (IB's improving on this as CoreData did for your issue). Seems you've seeing one of those situations now. My point: if you feel you're doing something the hard way, it may be still the best/right way.Goodluck – Nektarios Jun 15 '11 at 16:02
  • @Nektarios "if you feel you're doing something the hard way, it may be still the best/right way" is a good point. Apart from anything, you need to understand bindings and kvo/kvc well in order to understand what core data is doing and handle the edge cases. Writing this kind of stuff yourself can be a good way to do that. – Chris Devereux Jun 15 '11 at 19:16

1 Answers1

2

1) Nearly, but not quite. If you think of your application code being divided into three overall areas: Model, View and Controller (as documented here) then the Cocoa/XCode environment provides you with a 'code-free' way of handling the basics of each: IB for the view, Core Data for the model, and Bindings / Object Controllers for the controller.

Undo management is primarily a concern of the model, not the view or controller. So it's not really Bindings or the Object controller's job to manage this stuff. It looks like your problem is that you're using arrays as your data objects, which are too lightweight to handle this stuff. If you want undo support, you'll want to use core data to handle the model and give you this stuff for free, or hand-roll your own model objects, (which will probably contain arrays) which handle this logic.

FWIW, once you've done this, bindings will indirectly make your life much easier, as when an undo command reverts your data to its previous state, the view will automatically reflect the changes.

Also, NSArrayController's name is slightly misleading -- it isn't there to 'control arrays'. It's really for controlling data objects which have to-many relationships to other data objects. Which brings me on to...

2) KVC allows you to treat a to-many relationship between an object and other objects as an array or set, regardless of how the relationship is actually implemented. It does so by requiring you to implement methods fitting a naming convention, which very closely match the primitive methods of arrays and sets. KVC-compliant objects will return a proxy array or set when you call mutableArrayValueForKey: or mutableSetValueForKey:, which exposes those methods as an array. Roughly, that's how NSArrayController knows what to call --- KVC maps between the primitive objects of an array and some methods whose manes it generates from the key. Since you don't want to use arrays as your data objects, it's generally very useful to be able to treat any to-many relationship as if it were just an ordinary collection.

3) I think this is related to you handling undo in the wrong place. Implement KVC-compliant methods to get/set properties in your data objects, have them update the undoManger at the same time as setting the data. You'll need a special method for the undomanager to revert changes, as you don't want undos to be recorded as undoable. Or you could just use Core Data and get all this stuff for free...

Chris Devereux
  • 5,453
  • 1
  • 26
  • 32