CoreData - how to create a subclass of a subclass of NSManagedObject (Generation Gap pattern) - cocoa

I'm sure this must be a stupid question, but I've scoured the interwebbings and can't find the answer. Plenty of people talk about using the Generation Gap pattern, in which you have an NSManagedObject subclass that is generated from your model, and then subclass that to add transient properties and behaviours. The benefit of this is that if you change your persisted model, you can just generate your base files again without any danger of overwriting your own code.
I have a base CardMO object derived from NSManagedObject. I then subclass this to make my own Card object.
My question is, how do I create a Card object that is managed?
I tried:
Card* card = [NSEntityDescription insertNewObjectForEntityForName:#"CardMO" inManagedObjectContext:moc];
But this object isn't really a Card, and of course there's an exception when I go on to call a Card method on this object.
I've tried creating a Card with alloc / init (where init just calls [super init]), and then adding it to the managedObjectContext like this:
[moc insertObject:(CardMO*)card];
This gives me the cryptic error "Failed to call designated initializer on NSManagedObject class 'Card'"
I've tried modifying this by calling [NSEntityDescription insertNewObjectForEntityForName:#"CardMO" inManagedObjectContext:[AIStoreManager sharedAIStoreManager].managedObjectContext] instead of [super init]. In this case the object I get back is again a CardMO, and I can't call Card methods on it.
What should I be doing?
And (for bonus points :-) - after I've passed this hurdle, I need to create my other Card objects from XML, and then turn them into NSManagedObjects. I'm using code based on Apple's XMLReaderSAX - I hand this code a chunk of XML, and it gives me back an array of many Card objects. Can I later add these objects to my managed object context, or do I have to get into XMLReaderSAX and change how it creates those objects?
I'm writing an iPhone app on 3.0, but I assume this is the same for Core Data for 10.5.

Sussed it!
In the data model, the class name for the entity has to be that of the most derived class - Card in my case. The trouble then is that you have to remember to change the name to CardMO before generating any new files.
This must be why people use mogenerator.

Why not use NSCoding? Then you could support XML or JSON or any other means of describing the object outside your application.

Related

implementing custom accessor methods

I am reading "Core Data Programming Guide". It contains this text:
You must, however, change attribute values in a KVC-compliant fashion.
For example, the following typically represents a programming error:
NSMutableString *mutableString = [NSMutableString stringWithString:#"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:#"Laura"];
For mutable values, you should either transfer ownership of the value
to Core Data, or implement custom accessor methods to always perform a
copy. The previous example may not represent an error if the class
representing the Employee entity declared the firstName property
(copy) (or implemented a custom setFirstName: method that copied the
new value). In this case, after the invocation of setString: (in the
third code line) the value of firstName would then still be “Stig” and
not “Laura”.
Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?
Question regarding copy and programming practice:
From what I have read here:
NSString property: copy or retain?
I understand
that using copy will ensure that firstName is "Stig", not Laura
it is wise to do so because "in almost all cases you want to prevent mutating an object's attributes behind its back"
I would really like to know what is the above quoted text trying to tell us in the context of Core Data. We have to use "copy" anyway whether using Core Data or not. Also, I would be glad if someone could throw more light on point "2" (it is wise to...) above as in what will be the consequences of mutating an object's attributes behind its back?
your "Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?"
mis-matched the point that Apple document wants to explain, I believe.
As Apple document points out, if custom-accessor-method is implemented normally, the default implementation does NOT copy attribute values. If the attribute value may be mutable and implements the NSCopying protocol (as is the case with NSString, for example), you can copy the value in a custom accessor to help preserve encapsulation (for example, in the case where an instance of NSMutableString is passed as a value).
Here is a copying setter snippet
#interface Department : NSManagedObject
{
}
#property(nonatomic, copy) NSString *name;
#end
#implementation Department
#dynamic name;
- (void)setName:(NSString *)newName
{
[self willChangeValueForKey:#"name"];
// NSString implements NSCopying, so copy the attribute value
NSString *newNameCopy = [newName copy];
[self setPrimitiveName:newNameCopy];
[self didChangeValueForKey:#"name"];
} #end
The issue is when to use (and how) immutable values.
Since core data use KVO heavily when detecting changes done to objects, if you use a mutable property that is changed directly through it object and not through the property, CoreData will not detect the change to the object and your changes might not persist to the store.
If you use mutable NSManagedObject attributes, override the setter/getter method and use only them to mutate the underlying object (this mean that you are responsible to let CoreData know that a change did happen to the object, and it must be persisted to the store.
Also, if you use transformable properties for complex objects, you must trigger the change notifications yourself in order for CoreData to realise that a change has occurred, and the object should be re-transformed and saved when the context saves.
I would highly recommend that when it comes to simple objects like strings, you use immutable property values which will force you to go through the object properties and trigger the default KVO notification (copy attributes will also force the KVO notifications).

Understanding and Reproducing the KVC Hillegass Way to Insert/Remove Objects in/from Controllers

In Aaron Hillegass' Cocoa Programming for Mac OS X, the Raiseman application connects a button in Interface Builder (IB) to an NSArrayController with sent action -remove:. In the MyDocument class he implements two KVC methods:
- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index;
- (void)removeObjectFromEmployeesAtIndex:(int)index;
When this button is pressed, the -removeObjectFromEmployeesAtIndex: method is called and the currently selected Person (Model) object is removed from the array.
How does the remove: method used in IB cause the -removeObjectFromEmployeesAtIndex: method to be called?
How do I reproduce this effect with an NSTreeController?
If you want a simple built-in option, then it's only going to create an instance of the class you specified in IB. To create another instance, you're going to need to code it yourself. You should have all the information you need from the Tree Controller to insert the new class into the proper place in the hierarchy. Some diligent searching should give you the code you need.
To attempt to help you understand how the NSArrayController mechanism works, I'll explain the best I can from my knowledge of Objective-C and the runtime. Objective-C is a very dynamic language, and you can dynamically call selectors (methods). Since the NSArrayController knows the name of your class (e.g. "Employee"), its internal implementation probably looks something like the following (or easily could):
NSString *removeSelectorName = [NSString stringWithFormat:#"removeObjectFrom%#sAtIndex:",
self.objectClassName];
SEL removeSelector = NSSelectorFromString(removeSelectorName);
[dataRepresentation performSelector:removeSelector
withObject:[NSNumber numberWithInt:self.selectionIndex];
There are examples of this elsewhere in KVO, as with the +keyPathsForValuesAffecting<Key> method (documentation here), which describes which keys cause another key to be updated. If your key is named fullName and it updates whenever the first or last name changes, you would implement this in your class:
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:
#"firstName",
#"lastName",
nil];
}
Further searching (and this question) turned up this documentation page, which explains the semantics of how that method gets called.

Adding custom methods to a subclassed NSManagedObject

I have a Core Data model where I have an entity A, which is an abstract. Entities B, C, and D inherit from entity A. There are several properties defined in entity A which are used by B, C, and D.
I would like to leverage this inheritance in my model code. In addition to properties, I am wondering if I can add methods to entity A, which are implemented in it's sub-entities.
For example:
I add a method to the interface for entity A which returns a value and takes one argument
I add implementations of this method to A, B, C, D
Then, I call executeFetchRequest: to retrieve all instances of B
I call the method on the objects retrieved, which should call the implementation of the method contained in B's implementation
I have tried this, but when calling the method, I receive:
[NSManagedObject methodName:]:
unrecognized selector sent to instance
I presume this is because the objects returned by executeFetchRequest: are proxy objects of some sort.
Is there any way to leverage inheritance using subclassed NSManagedObjects?
I would really like to be able to do this, otherwise my model code would be responsible for determining what type of NSManagedObject it's dealing with and perform special logic according to the type, which is undesirable.
Any help is appreciated, thanks in advance.
It should work. The objects returned by executeFetchRequest: are real instances of NSManagedObjects (or subclasses thereof.)
The steps to use custom classes in CoreData are as follows. Say you have entities A and B, where B inherits from A.
Then you need two custom classes as
#interface A:NSManagedObject{
}
-(void)someMethod:(NSString*)a;
#end;
#interface B:A{
}
-(void)someMethod:(NSString*)a;
#end;
Then set them in the XCode data modeler as shown:
This way, the CoreData automatically assigns the correct class to the NSManagedObject when it is fetched from the database.
If you're getting that exception, it means Core Data is not using your custom class. The key here is NSManagedObject -- that's the object Core Data created for the objects in your data store.
If you haven't already, you'll need to create a class that inherits from NSManagedObject, add your custom methods there, and then set entity A to use your custom class in the object model tool. If entities B, C, D, etc. have specific behaviors, you should subclass the class you created for entity A and assign those entities to use the subclasses too.
Essentially, you have a parallel hierarchy: one hierarchy of entities, and another of classes. You'll likely end up with entity X and class X for each entity in your object model.
After trying lots of solution calling isMemberOfClass on my NSManagedObject subclass before trying to use my custom method made the trick.
[thing isMemberOfClass:[Thing class]];
[thing customMethod]; //was getting unrecognized selector sent to instance here before
I had this same error for the same underlying reason, but it came about in a different situation and a different cure. Your suggestion helped me a lot!
Originally I had created my class implementing my entry by hand. I didn't know there was an Xcode menu for this. I think the link was never there! So it wasn't until I had added and began testing the new custom methods (not setter/getters) that I started to get the error.
My solution was to change the name of my class, have Xcode re-create the class for my entry via Editor->Create NS Mangage Object.... Then cut and paste in the old code into the new class. No difference in code!
Xcode seems to have some kind of internal link that is not apparent in the code.

Can a NSManagedObject own another as a property?

I've taught myself Obj-C, and have been self-teaching Cocoa, but adding Core Data to my program has given me nothing but a huge headache, thanks to me needing extensive custom logic. Here are a couple of the questions that are driving me insane.
What if I want a Managed Object to own another Managed Object? It seems if I give it a to-many relationship, the owned object will simply be shared by various masters, but I want each Owner to have its own.
If I subclass an NSManagedObject, can I make simple calls to the Array Controller to remove or copy instances of ManagedObject, and assume those will be translated into the Core Data model?
If I want to programmatically edit the properties of a ManagedObject, can I get away with mere KVC calls to the Array Controller? What's all this talk about NSPredicate an NSFetchRequest to the NSManagedObjectContext from the NSManagedDataStoreDrivingMeCrazy? Can I make an NSFetchRequest that filters the relationships of an object currently selected in a table view?
Once I use a fetch request to get a group of objects, how do I go about querying their relations? Does that require a whole other FetchRequest, Predicate, and so forth? Isn't Core Data supposed to be easier? Am I missing something?
An entity is similar to a class--it's a blueprint for a managed object that will be instantiated later. Each managed object will have its own attributes and relationships to configure.
You can definitely insert and delete managed objects. You might have to do some code to support copying, but I am not sure.
Yes, the properties (attributes and relationships) of managed objects support KVC (and KVO and bindings).
You can access the object or set of objects simply by using the relationship name that you define in the model (no additional fetch or logic is required).

NSManagedObject as NSDictionary key?

In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.

Resources