How to observe a Core Data relationship before it gets actually changed? - objective-c

I am trying to find a way of observing a Core Data relationship (more specifically the removed items of an unordered relationship) before it gets actually changed. You can imagine it as a "will change" notification known from other Cocoa APIs.
The reason is that I want to update the UI according to these changes and I need a parameter which isn't stored in the managed objects but in an other data structure (in my case it's the index from a NSArrayController). I can retrieve the parameter only if the items weren't removed from the relationship yet.
The only way of getting such "will change" notifications I could find was to override the KVO method -willChangeValueForKey:withSetMutation:usingObjects: in the custom NSManagedObject subclass and forward this information. The problem is the documentation says: "You must not override this method." and this makes me want to find a better solution.
Is the "will change" approach right in this case?
If so, how could I achieve it? If not, how should I solve the mentioned problem in another way?

You can just override the relationship’s setter method instead.

Related

iOS synchronization - what is a good way for a Core Data object to know it has to be pushed to server?

I am building sync functionality between an iPad and a web server. I'm using an approach pretty close to the one described here. I only have one type of object, let's called it a Story, that has to be synchronized. It is a Core Data entity (managed object).
The remaining problem I have to solve is knowing "whenever something changes and needs to be synchronized to the server." One approach would be to go find every piece of code that modifies a Story and have it also set some needsSyncing flag. That does not seem elegant and it seems that over time, developers could forget to update the flag for new types of modification.
Do Core Data objects have a way to observe themselves, so any time any change is made, a particular method is executed? That would make this pretty easy.
Another option might be using the isUpdated method right before doing a save operation on the managed object context. You'd either have to have save called in only one place or do this at every place you save (sounds like the first option). I guess I could make a helper method that goes through all Story objects right before saving and see if any of them need their flag to be set. The only drawback to that is that I'd be traversing all Story objects in the system for any save, even for saves that have nothing to do with a Story.
Anyway I'll stop trying to guess the solution out loud - does anyone have experience with a good way to do this?
SDK has you covered. See the NSManagedObjectContext class reference, at the very end of the page, the MOC will post notifications that you can subscribe to, including NSManagedObjectContextObjectsDidChangeNotification. You can listen for these and do the update call pretty much coincident with saving the MOC.

Customizing core data generated accessors

I have set up a simple core data schema, where item objects are added to list objects. After generating classes, core data has generated the standard accessors, including a couple of ways to add items to a list (list.addItemsObjects and list.addItems:).
I also want to add a 'dateAdded' NSDate to each item. Now, I could manually set this every time I create an object, since core data will provide me with an item.dateAdded accessor. But really, I would prefer that this is handled by the list itself, since the implementation will never change. So every time I call list.AddItemObject, I would like it to set the dateAdded attribute of the item at the same time. I guess I would also need to do the same in the inverse, so that item.setList also sets the dateAdded.
I am not sure how to do this. Should I try to redefine the existing core data-generated accessors? If so, how do I do this (is there someway to call the original implementation inside my custom code, such that the inverse relationship is handled, and any other core data necessities)? Or is there some better way to customize these methods?
You can override the core data generated accessors.
You have to pay attention to a few special things like calling willChangeValueForKey and didChangeValueForKey, but other than that overriding the accessors is pretty much the same as always.
Read the Apple documentation: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdAccessorMethods.html
It's explained clearly with lots of examples. Just pay attention to the difference between one-to-one and one-to-many relations.
You do not need to implement the inverse functionality, this is done by Core Data. If you call list.AddItem, then item.setList will automatically be called for you.
So just add your desired code to the item.setList accessor. That way, you guarantee that everytime an item is added to a list the date is updated accordingly. Don't bother with the list-accessors.
Your code would be something like:
- (void)setList:(List *)value
{
[self willChangeValueForKey:#"list"];
[self setPrimitiveValue:[NSDate date] forKey:#"dateAdded"]; // use setValue:forKey: if you need KVO for dateAdded
[self setPrimitiveValue:value forKey:#"list"];
[self didChangeValueForKey:#"list"];
}
EDIT I've given it another thought and you might want to know the following:
If you customize the list methods (ie addItemObject), you'll also need to customize setItems. Also, if your item is added to a list which happens not to be the list you customized, your custom code is of course not called.
On the other hand, if you customize the setList method, the code is not called if there are no changes for the item.
For example, if you call
[list addItems:[NSSet setWithObject:item]];
[list addItemsObject:item];
then the item's setList accessor will only be called once! Calls directly to item.setList are always called, even if nothing changed.

Objective-C undo manager questions

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:
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?
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?
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?
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...

Elegantly add object to arraycontroller, which itself is linked to another arraycontroller?

I'm new to OS X programming but generally liking it. I have the following problem:
I have two core data entities linked through a one-to-many with their respective arraycontrollers (Stock Controller and Price History Controller, where the latter controller is bound to the Stock Controller, with Controller Key = selection and Model Key Path = priceHistory, which is the relationship that links the stock entity to the PriceDataPoint entity, controlled by the Price History Controller.
This all works like magic in my UI, where I can select stocks and add/remove price points to each one when it is selected. However, I need to be able to do this programmatically as well.
If I simply call [stockController add:self] the UI updates with new objects with the correct default values, linked to the selected stock -- even though 'self' is not the correct class/entity. This is one point of confusion for me, which I don't understand (I understand that the Stock Controller knows about the selected stock through the KVO binding and would likely set the relationship as required, but I don't get how it 'casts' 'self', which is a fairly random class into the object type required (a plain NSManagedObject)? Secondly, if I do this, how do I get a reference back to that object so I can edit its values?
More importantly, however, if I then follow the Apple examples, create a new NSManagedObject through NSEntityDescription:insertNewObjectForEntity: and use [stockController addObject:Newly created Object] I can write the values I want before adding it but the relationship to the 'parent' stock is not set by the addObjects: method. I am sure I can figure out how to write this, too but with everything else in Cocoa being so elegant this just feels odd so I am hoping that someone here can clear this up very quickly and point me to an elegant way of doing it.
First, let's dispel the magic by stating Core Data is built to work with the Cocoa Bindings mechanism (which gives you array controllers, among other things) and is built atop Key Value Coding / Key Value Observing mechanisms. The Core Data documentation clearly states you should have a good background in these technologies to understand how / why things are working.
For your first issue, you said you're confused by the -add: method of NSArrayController. Have you looked at the signature/prototype? The argument (where you pass self) is (id)sender, which is the hallmark of a basic action (see "target/action" in the docs). You can pass self, some other object, or even nil if you want. The argument is NOT the object you wish to add to the collection managed by the array controller.
For your second issue, you're on the right track if you want to customize things or keep a reference to the newly-inserted object. You can use the -addObject: method (which does take the object to add as its argument) but you'll have to tell the array controller to -fetch: (another action w/sender as argument) in order to refresh the controller AND anything bound to its contents (such as a table/outline).

Proposed solution to NSTreeController displaying duplicate entities

As many of you may know, an NSTreeController bound to an outline view can display duplicates while presenting core data entities.
A temporary solution is to add 'parent == nil' to the predicates, but this only returns parent entities. If, for instance, a user is searching for a sub-entity, the requested sub-entity won't be displayed.
A (proposed) solution is to subclass NSTreeController and add a NSMutableSet variable, which keeps track of entities that are currently being displayed. This variable should be alloced on init, and released on dealloc.
When "fetchWithRequest:merge:error:" is called, the set should be emptied (I'm not sure whether this would be more efficient than releasing it and allocating it again). Everytime an entity is going to be added to display, check if the set contains it. If it doesn't, add it. Otherwise, find which is closer to the root (which is the subentity) and either skip it if its the subentity, or swap it with the previously included one.
I think there should be relatively little impact on performance (considering NSSet uses hashing). The problem I'm having is finding the correct method to override to add this behavior. Specifically, where NSTreeController processes fetched entities after "fetchWithRequest:merge:error:" is called.
Is it fair to say you're really looking for a way to filter the tree with a search term without losing the tree structure? The inherent problem (beyond forcing the tree controller to include the parent nodes of a search match) is that the parents may or may not actually match the search result, so it's confusing to display them.
I think yours is more a problem of UI, isn't it? In that case, the best approach (and one I've seen many well-known companies and independent developers take) is to display search results in a plain table. This way the results can be sorted by various attributes and you don't have to disable drag and drop in the outline view in search mode (to avoid the user trying to change the tree structure when only part of the tree is displayed out of context).
Expanding on Joshua's answer, I was able to implement Search Functionality into my own NSOutlineView, however it was limited to the root/parent objects in the view.
I think (like Joshua said) if you wanted to filter all objects you would have to display the results in a NSTableView.