Customizing core data generated accessors - objective-c

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.

Related

How to pass the model to a Controller in Core Data

Say I have a Core Data class called RecipeBook which has a property (relationship) called recipes, a NSSet of Recipe Objects.
I display the RecipeBooks on a UITableView and when the user taps on a cell, it should display the Recipes on another UITableViewController.
What should I pass as the model to this last UITableViewController:
a context and a fetchRequest
or the NSSet of Recipe objects?
If there's a change to the db will the NSSet "automagically" update?
From your description I'd probably pass the selected RecipeBook instance. From that I can (presumably) get all the recipes contained in the book and display them in the table. That assumes that a relationship exists from RecipeBook to Recipe which-- based on your description-- should be true. If I need to do any other work with the data store, I could ask the RecipeBook for its managed object context and work with that.
No NSSet of fetched objects is going to update automatically. But the relationship from a RecipeBook to its Recipes will update any time a recipe is added or removed from the recipe book.
And finally-- passing any of this directly to a UITableView doesn't make a lot of sense. Apple's iOS frameworks are designed with MVC in mind, and going against that will make things a lot harder than they need to be. If you have a UITableViewController, you could pass your model objects to that.
Generally, you shouldn't pass "model" objects to view objects.
My favorite way to think about it is that views are actually another form of a model (think of them both as simply representations of data). The controller's job is to ensure that neither representation needs to know anything about how the other stores it's representation.
So basically, your controller will be your data delegate, and is responsible for properly populating table cells with it's own references to your core data models.
The automagically question depends on your core data setup, but usually the answer is yes.
By default, CD uses key-value coding, which simply lets you access properties with valueForKey:. More advanced setups involve having Xcode generate classes for you, in which case a few mouse clicks get you "dot notation" accessor methods regenerated from an updated model.

Core Data, iVars and Categories

One of my core data subclasses has an NSSet of items. It is often (but not always, so no NSOrderedSet) useful to instead retrieve an ordered NSArray, so I added orderedItems to the class, which sorts them.
I then ran into performance issues so decided to try caching the orderedItems. My plan is to use an iVar, _cachedOrderedItems in the class, which I will return if it is not null.
The snag comes with my use of categories. I read some good advice about putting all of my custom code in a category so that I can re-generate the core data class if necessary and not lose all my customizations. One of those customizations is the orderedItems method.
It seems I can not declare an iVar in the category itself. And if I try to put it in the core data class instead, I can not access it in the category.
Do I need to move my custom code back into the core data class? Or am I missing something?
I have also heard about Mogenerator, and would consider learning to use this if it would help.
You can use associative references to add ivars to a class any time you can't modify the original class, including in categories. For a detailed example, see Faking instance variables in Objective-C categories with Associative References.
You own the class, so you can use a class continuation (discussed here) instead of a category. This allows you to add instance variables.
You should definitely use mogenerator. See for example http://importantshock.wordpress.com/2006/12/19/mogenerator-or-how-i-nearly-abandoned-core-data/.
You can make an Aggregate target in XCode, add a Run Script with the following:
mogenerator -m path/to/your/datamodel.xcdatamodeld/version.xcdatamodel --template-var arc=true -M /CoreData/Generated -H /CoreData
For every NSManagedObject you get a class and a subclass. When updating your datamodel, run the script again and the base class will be updated, preserving all the changes you made to the managed object subclass. Remove --template-var arc=true for none arc.

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

Core Data - Managed object question

I have 2 basic questions regarding Core Data;
What exactly is a managed object -- Is it equivalent to 1 instance of a class. For example, if there is an entity called Shape which has attributes like no of sides & color and if there are 5 entries displayed in a table, does it mean there are 5 managed objects each with attributes no. of sides & color. I am a bit confused about this basic concept.
What exactly is the relationship between Fetched Results Controller (FRC) and a table view? I know the delegate methods, but how exactly is the table view impacted/related to FRC ?
Any basic examples will be really useful. Thank you.
Q1. A managed object is a representation of some entity that has been persisted by an application. It is simply a generic abstraction away from the actual persisted type. So you are right in saying that the managed object will have the same keys/properties as the concrete type. As the NSManagedbject class implements the key-value coding pattern you can query its key/values at runtime...
- (id)valueForKey:(NSString *)key
For full documentation on NSManagedObject see here
Q2. The fetched results controller is what your UIViewController is to your UIView. It contains the logic that controls persistence for your table view. Its sole purpose in life is to keep database handling logic out of your UITableView. It does so by allowing you to define the behaviour you want to execute in the context of your UITableView. What I mean by this is that its delegate provides methods with signatures that explicitly imply an effect on a UITableView.
Q1. Yes, there are 5 managed objects out there and available to you. You could put all five in an array if you wanted to. Managed Object is simply a term that means you have code to manage the Insert, Change, and Delete actions into the database - in otherwords, some code manages it through its life cycle. You get at it through an FRC, the FRC ^fetches^ instances of the object from the database, and allows your code to ^control^ what happens to the ^result^. Hence the name.

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).