Can I encode a subclass of NSManagedObject? - iphone

This is for an iPhone App, but I don't think that really matters. I need to send a custom object (which is managed by Core Data) over bluetooth using the iPhone's GameKit. Normally, I would just use an NSKeyedArchiver to package up the object as a data object and ship it over the line, then unarchive the object and I'm done. Of course, I would need to implement the initWithCoder: and encodeWithCoder: methods in my custom object as well.
I'm not sure if this can be done with an NSManagedObject class, which is managed by Core Data or not. Will they play nice together? I'm guessing once I ship the encoded managed object over to the other device and unencode it, I would just add this received object to the other device's context. Is this correct? Am I missing any steps?

An NSManagedObject instance can't meaningfully exist outside of an NSManagedObjectContext instance, so I wouldn't bother trying to do the NSCoding dances required to directly serialize and deserialize an NSManagedObject between two contexts (you can do this; see below). Instead I would create a dictionary with the appropriate attribute key/values (you can get the attribute names via the managed object instance's attribute names via instance.entity.attributesByName.allKeys (you can use [instance dictionaryWithValuesForKeys:keys] to get the dictionary of attribute:value pairs) . I would send relationship information as NSURL-encoded NSManagedObjectIDs. Don't forget to include the instance managedObjectID (as an NSURL) in the dictionary so that you can reconnect any relationships to the object on the other end. You'll have to recursively create these dictionaries for any targets of relationships for the instance you're encoding.
Then send the dict(s) across the wire and reconstitute them on the other end as instances in a new managed object context (you can use setValuesForKeysWithDictionary:).
You may notice that this is exactly what the NSCoder system would do for you, except you would have to use the classForCoder, replacementObjectForCoder: and awakeAfterUsingCoder: along with a custom NSDictionary subclass to handle all the NSManageObject-to-NSDictionary mapping and visa versa. This code is more trouble than it's worth, in my experience, unless you have a complex/deep object graph that you're trying to serialize. For a single NSManagedObject instance with no relationships, it's definitely easier to just do the conversion to a dict and back yourself.

This sounds like a job for TPAutoArchiver.

I suggest the dictionary solution for simpler options. However, here is how I solved the issue. My model was already sizable and robust, with custom classes and a single root class above NSManagedObject.
All that I needed was for that single class to call the appropriate designated initializer of NSManagedObject: [super initWithEntity:insertIntoManagedObjectContext:]. This method, and the metadata in an NSEntityDescription is what sets up the implementations of all the dynamic accessors.
- (id)initWithCoder:(NSCoder *)aDecoder {
CoreDataStack *cds = [LibraryDiscoverer unarchivingCoreDataStack];
NSEntityDescription *entity = [cds entityDescriptionForName:[[self class] entityName]];
NSManagedObjectContext *moc = [cds managedObjectContext];
self = [super initWithEntity:entity insertIntoManagedObjectContext:moc];
self.lastEditDate = [aDecoder decodeObjectForKey:#"lastEditDate"];
return self;
}
The CoreDataStack is my abstraction around CoreData. The LibraryDiscoverer is a global access hook to get hold of the core data information. The entityName is a method defined to provide the entity name from the class name; if you follow a naming convention (i.e. class name = entity name) it can be implemented generically.
All the other initWithCoder: methods in my class hierarchy are standard NSCoder, with the note that you don't need to encode both directions of a relationship, CoreData reconnects that for you. (As it always does, including with the dictionary solution.)

Related

Creating instance for NSManagedObject Class using Restkit

how can i create an instance for NSmanagedObject Class which i am created using core data model. I have another class which is the subclass of NSObject. I want to create an object for NSManageObject Class. how can i do that. Is it like the normal way that we create the instance for NSobject class like
ClassB.m //ClassB is NSObject Class
ClassA *obj = [[ClassA alloc]init]; //ClassA is NSmanagedObject Class
or is there any way to do that???
i am not using this code(Apple Docs) for creating instance
NSManagedObject *newEmployee = [[NSManagedObject alloc]
initWithEntity:employeeEntity
insertIntoManagedObjectContext:context];
i would like to know, is there any way to create an object similar to the above code using restkit
Note: i am using Restkit for creating object instance and mapping.
Thanks
Using Restkit you can use the static method object on the class you want to create. In your example
[ClassA object]
would return you an instantiated object.
Just be sure to import the correct headers:
#import <RestKit/CoreData.h>
instead of
#import <CoreData/CoreData.h>
No, you don't want to instantiate an NSManagedObject via alloc/init. You should use NSEntityDescription's insertNewObjectForEntityForName:inManagedObjectContext:. Something like:
ClassA *obj = [NSEntityDescription
insertNewObjectForEntityForName:#"ClassA"
inManagedObjectContext:context]; // ClassA is NSManagedObject Class
In order to have a NSManagedObjectContext, you also need an NSPersistentStoreCoordinator, and an NSManagedObjectModel, etc. Yeah, it's complex. This book really helped me get my head around Core Data; I recommend it wholeheartedly.
See the Core Data Programming Guide section on creating and deleting managed objects. Actually, while you're there, I'd recommend reading the entire Core Data Programming Guide. Core Data is amazing and powerful, but it is complex; you really want to know what you're doing.
Update: The fact that you're using RestKit might change what I said above. It may be that RestKit has it's own API for doing Core Data stuff, I'm not sure. Maybe check the docs.

iPhone Core Data - cannot fulfill a fault error

I have three classes, A, B and C. A is the main class.
When the user wants to see the list of all objects that were purchased, Class B is called from A and shows the list of objects in a core data entity.
Inside class B, the user can buy new objects (in-app purchase). When the user wants to buy another object, class C is called.
When class C is called, a new object is created on the core data entity using
anObject = [NSEntityDescription insertNewObjectForEntityForName:#"Objects" inManagedObjectContext:context];
this object is then assigned to a local reference on Class C, using something like
self.object = anObject;
this object variable was declared like this:
.h
MyObjects *object;
#property (nonatomic, retain) MyObjects *object;
and #synthesized on .m
MyObjects is a core data class representing the entity.
In theory, object will retain anything assigned to it, so the line self.object = anObject I typed previously will retain anObject reference on self.object, right?
The problem is that when I try to access self.object in the same class after buying the new object, I receive an error "CoreData could not fulfill a fault for XXX", where XXX is exactly self.object.
At no point in the code there's any object removal from the database. The only operation to the database I could identify was a saving operation done by another class moments before the crash. The save is done by something like
if (![self.managedObjectContext save:&error]) ...
Is there any relation? what may be causing that?
CoreData manages the lifetime of managed objects and you should not retain and release them. If you want to keep a reference to the object so that it can be retrieved later then you have to store the object's id (obtained using -[NSManagedObject objectID]). Then use that to retrieve the object later using -[NSManagedObjectContext objectWithID:].
Make sure you understand about CoreData faulting. Read the documentation.
I had a similar issue a few days ago (using NSFetchedResultsController) where I was placing my fetchedObjects into an array and gathering attributes to populate tables from the array objects. It seems that if the objects in the array are faulted, you cannot unfault it unless you are acting on the direct object. In my case, I solved the issue by taking the lines of code in question and calling [[_fetchedResultsController objectAtIndexPath:indexPath] someAttribute]. I would assume that doing something similar would fix your problem as well. It seems a bit tedious to need to fetch from the managedObjectContext to obtain a faulted value, but this was the only way I could personally get past the issue.
Core Data is responsible for managing the lifetime of managed objects in memory. It's really important to understand Managed Object Contexts - Read the documentation.
Apple also provides an entire troubleshooting section here, and it contains among other things the causes for your error. But it's really only useful if you understand how core data works.
Most likely error is that the object you are saving does not belong to the managed object context.
Say you use the same object on different threads and those different threads use different managed object context, then this will happen.

Retrieving data from core data

I'm using an xcdatamodel to define a number of classes based upon CoreData data entities. This is working great and I can retrieve them in accordance to Apple's examples:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html
What I'm after however, is a way to package the fetch method up into another class, but I have a couple of questions...
e.g.
MyDataAccessClass *mdac = [[MyDataAccessClass alloc] init];
myFetchedData = [mdac fetchData];
Q1. If I do this, is it ok that the NSManagedObjectContext is defined in the class? or does it still need to be referenced in my view controller and passed to my 'MyDataAccessClass'?
Q2. It makes sense to me to have the data retrieval methods set up in the classes created by XCode for the entities in the data model. Although every time I try to do this, then update those classes automatically because they are automatically generated by XCode, they overwrite any methods I've defined.
Thanks in advance!
You might be able to create a new NSManagedObjectContext in the seperate class - not sure if there will be any issues with that since one is already created in the appDelegate. What I did was what you suggest in the second part of Q1, where I pass the NSManageObjectContext into the seperate method class so then I can do something like:
myFetchedData = [mdac fetchData:currentNSManagedObjectContext];

iPhone - Change entity class (NSManagedObject) to make them initializable

I would like to use my custom NSManagedObject like a normal object (as well as its regular functions). Is it possible to modify the class in order to be able to initialize it like a normal object?
[[myManagedObject alloc] init];
Thanks
edit: to clarify the question, will it screw everything up if I change the #dynamic with #synthesize in the implementation?
I do this quite often in one of my apps. My approach is to initialize the object with:
-(id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context
passing nil for the context. To get the entity description, you do need access to the managedObjectContext. I tend to get the entity description when my app starts and then store it in an instance variable in my app delegate.
Here's an example:
//inside my "Engine" class
self.tweetEntity = [NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:self.moc];
//later on when I want an NSManagedObject but not in a managed object context
Tweet *tweet = [[[Tweet alloc] initWithEntity:self.engine.tweetEntity insertIntoManagedObjectContext:nil] autorelease];
This allows me to use NSManagedObjects without storing them in a database. Later on, if I decide I do want the object inserted into my database, I can do it by inserting it into my managed object context:
[self.moc insertObject:tweet];
The managed object context is a required property of NSManagedObject therefore you can't properly initialize an instance of it without inserting it into a context. It looks at the context to understand it its entity and it notifies the context when any of its properties change.
The #dynamic and #synthesize are just compiler directives. You can switch to #synthesize from #dynamic as long as you provide proper getters and setters yourself. Since NSManagedObject relies heavily on key-value observing to function, you do have to write KVO compliant accessors.
If you need to initialize an NSManagedObject subclass, you override awakeFromInsert which will let you provide customization to the instance when it is created. You can also customized the object every time it is fetched using awakeFromFetch.

Can I create an new instance of my custom managed object class without going through NSEntityDescription?

From an Apple example, I have this:
Event *event = (Event*)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
Event inherits from NSManagedObject. Is there a way to avoid this weird call to NSEntityDescription and instead just alloc+init somehow directly the Event class? Would I have to write my own initializer that just does that stuff above? Or is NSManagedObject already intelligent enough to do that?
NSManagedObject provides a method called initWithEntity:insertIntoManagedObjectContext:. You can use this to do a more traditional alloc/init pair. Keep in mind that the object this returns is not autoreleased.
I've run into the exact same problem. It turns out you can completely create an entity and not add it to the store at first, then make some checks on it and if everything is good insert it into the store. I use it during an XML parsing session where I only want to insert entities once they have been properly and entirely parsed.
First you need to create the entity:
// This line creates the proper description using the managed context and entity name.
// Note that it uses the managed object context
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Location" inManagedObjectContext:[self managedContext]];
// This line initialized the entity but does not insert it into the managed object context.
currentEntity = [[Location alloc] initWithEntity:ent insertIntoManagedObjectContext:nil];
Then once you are happy with the processing you can simply insert your entity into the store:
[self managedContext] insertObject:currentEntity
Note that in those examples the currentEntity object has been defined in a header file as follows:
id currentEntity
To get it to work properly, there is a LOT of stuff to do. -insertNewObject:... is by far the easiest way, weird or not. The documentation says:
A managed object differs from other
objects in three main ways—a managed
object ... Exists in an environment
defined by its managed object context
... there is therefore a lot of work
to do to create a new managed object
and properly integrate it into the
Core Data infrastructure ... you are
discouraged from overriding
initWithEntity:insertIntoManagedObjectContext:
That said, you can still do it (read further down the page to which I linked) but your goal appears to be "easier" or "less weird". I'd say the method you feel is weird is actually the simplest, most normal way.
I found a definitive answer from More iPhone 3 Development by Dave Mark and Jeff LeMarche.
If it really bothers you that you use a method on NSEntityDescrpiton rather than on NSManagedObjectContext to insert a new object into an NSManagedObjectContext, you can use a category to add an instance method to NSManagedObjectContext.
Create two new text files called NSManagedObject-Insert.h and NSManagedObject-Insert.m.
In NSManagedObject-Insert.h, place the following code:
import <Cocoa/Cocoa.h>
#interface NSManagedObjectContext (insert)
- (NSManagedObject *)insertNewEntityWithName:(NSString *)name;
#end
In NSManagedObject-Insert.m, place this code:
#import "NSManagedObjectContext-insert.h"
#implementation NSManagedObjectContext (insert)
- (NSManagedObject *)insertNewEntityWithName:(NSString *)name
{
return [NSEntityDescription insertNewObjectForEntityForName:name inManagedObjectContext:self];
}
#end
You can import NSManagedObject-Insert.h anywhere you wish to use this new method. Then replace the insert calls against NSEntityDescription, like this one:
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
with the shorter and more intuitive one:
[context insertNewEntityWithName:[entity name]];
Aren't categories grand?