How to save my own object with NSKeyedArchiver? - iphone

i've got problems using the NSKeyedArchiver/NSKeyedUnarchiver for saving my object.
I added the methods - (id)initWithCoder:(NSCoder *)decoder and - (void)encodeWithCoder:(NSCoder *)encoder. The problem is when i try to save the object it doesn't work correctly.
I could imagine a problem (but I'm not sure if it is the problem ;) ). I have some arrays within my object. They contain more objects (I implemented both of the "Coder"-Methods as well). So does the array call the methods in it's objects?
Any possible solution??
Thanks!!

In the header file indicate that your class will implement the NSCoding protocol, like <NSCoding>
In the encodeWithCoder method you need to encode all the fields you want to save like so:
[encoder encodeObject:array1 forKey:#"array1"];
Then in the initWithCoder method, decode the fields that were encoded:
array1 = [coder decodeObjectForKey:#"array1"];
Be sure that any encoded containers only contain objects that also implement the NSCoding protocol. This could be core classes such as NSString, NSNumber, NSArray, NSDictionary, as well as your own custom object.
If your project is not using garbage collection you need to retain or copy the data retrieved from the archive like so:
array1 = [[coder decodeObjectForKey:#"array1"] retain];

Related

NSCoding with Nested Custom Objects?

I have a series of nested objects that I am needing to put through the NSCoding protocol so that I can save the top level object into NSUserDefaults.
Here is the structure of objects:
'Instructor' class
NSMutableArray that holds instances of...
'Class' class
NSMutableArray that holds instances of...
'Student' class
Name Property
Number Property
Money Property
I am needing to save an instance of Instructor to NSUserDefaults or to documents for the app. As you can see the Instructor object is holding an array that is then holding instances of a class. That class object is holding instances of students.
Is the NSCoding protocol recursive? What I mean by that is if I add the NSCoding protocol to each class, I could then save an Instructor object and it would recursively encode the contained objects?
Would it then work the same way while decoding? I could just decode the one instructor class and it would recursively decode the objects contained because they also conform to the NSCoding protocol?
How could I go about setting this up?
Yes, you can (and probably should) write your support for NSCoding to be recursive. That's how NSCoding is supposed to work.
When your implement encodeWithCoder, simply call
[coder encodeObject: aProperty forKey: #"propertyName"];
on all your object's properties, including it's container properties.
Then make sure every object in your object's object graph also conforms to NSCoding.
For scalar properties, you can save the scalar value using NSCoder methods like encodeInt:forKey:
To save an object that conforms to NSCoding to user defaults, first convert it to NSData using the NSKeyedArchiver class method archivedDataWithRootObject, then save the resulting data into defaults. Quite simple, really.
NSCoding isn't magic so it will work 'recursively' if your implementation of the encoding and decoding methods tells it to be.
Implement the NSCoding methods and pass the data to be encoded to the encoder. Implement the NSCoding methods in all of your custom classes so that when you encode the array all of the contents can be processed appropriately.
Be sure to call super if the classes superclass also implements NSCoding.
e.g.
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.arrayOfClasses forKey:#"arrayOfClasses"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.arrayOfClasses = [decoder decodeObjectForKey:#"arrayOfClasses"];
}

Mutable sets in NSManagedObjects?

The standard declaration for an autogenerated NSManagedObject property declares to-many relationships like this:
#property (nonatomic, retain) NSSet *somethings;
...
#dynamic somethings;
Would it be safe to change that declaration to an NSMutableSet? Would the CoreData component still function correctly?
Judging by the #dynamic, it probaby doesn't care if I use an NSSet subclass rather than an NSSet, but I don't want to rewrite a lot of code only to find out it doesn't work.
According to Apple's Core Data Programming Guide, this should always be declared as a NSSet.
If you want a mutable proxy (which is basically the mutable version of the core data set and works exactly the same) then you would use the function mutableSetValueForKey like this:
// myManagedObject is the managed object that has the somethings property.
NSMutableSet *mutableSomethings = [myManagedObject mutableSetValueForKey:#"somethings"];
mutableSomethings can then be modified as a standard NSMutableSet andsomethings will be updated and KVO methods will be appropriately called.
Note however, that many features of the mutable set (such as addSomethingsObject and removeSomethingsObject) are already provided in the core data generated accessors so in many cases you don't need to use the proxy.
You should keep it as an NSSet and do one of the following:
Use key value coding
Add the core data generated accessors
For key value coding, you'll access your collection like so:
NSMutableSet *somethings = [object mutableSetValueForKey:#"somethings"];
[somethings addObject:newObject];
For core data generated accessors, you'd add the following to your header:
#interface MyManagedObject (CoreDataGenerated)
- (void)addSomethingsObject:(MySomething *)object;
- (void)removeSomethingsObject:(MySomething *)object;
- (void)addSomethings:(NSSet *)somethings;
- (void)removeSomethings:(NSSet *)somethings;
#end
You do not need to implement these methods (Core Data will take care of it for you), and you can call them easily. This is my preferred way of handling collections because you get better type checking from the compiler.
The reason you should not change it to an NSMutableSet is because there is a good chance that you will cause crashes and/or that your changes will not be persisted into your Core Data store.
You may also want to look into mogenerator to help you out with creating all of your Cocoa classes.

Can I encode a subclass of NSManagedObject?

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

Saving an NSMutableArray to Core Data

I want to add an NSMutableArray of NSStrings to one of my Entities in my core data model. The problem is that this isn't a supported type in Core Data.
I tried making a tranformable attribute, but the problem is that I see no way of turning a NSMutableArray to NSData, and then going from NSData, back to an NSMutableArray. Does anyone have an idea as to how this issue can be solved?
(I know I can archive the array, but I don't want to do that, I want it to be present in my model).
You could have a binary data attribute in your modeled object, archive the array to data, and hand it off to the object.
But I think the better way would be to have a to-many relationship, instead of using an array directly.
****Edit: Here's how to archive the array into NSData so that it can be used in your managed object***
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:[NSArray arrayWithObjects:#"1",#"2", nil]];
Basically, any class you have which conforms to the NSCoding protocol can be archived in this way. NSArray/NSMutableArray already conform to it. They tell all of their objects to archive themselves, so they must conform too. And all of those objects' members must conform, etc. It's like a tree.
Since your array conforms, and it's an array of NSString (which also conforms), then you're golden.

Encode NSArray or NSDictionary using NSCoder

I was wondering whether or not it is possible to use the NSCoder method:
- (void)encodeObject:(id)objv forKey:(NSString *)key
to encode either an instance of NSArray or NSDictionary. If not how do you go about encoding them? I am trying to use NSKeyedArchived / NSKeyedUnarchiver to send data between phones using GameKit. I tried it and cannot seem to retrieve the string I stored in my array. The packet class I made comes through along with the packet type integer, but not the data in the array it seems.
Thanks in advance!
If the array or dictionary is the root object you should do
NSData * encodedData = [NSKeyedArchiver archivedDataWithRootObject:someArray];
or
BOOL success = [NSKeyedArchiver archiveRootObject:someArray toFile:filePath];
If it is an instance variable of a custom class, in the -encodeWithCoder: method should do
[coder encodeObject:someArray forKey:#"someArray"];
and then in the -initWithCoder: method
someArray = [[coder decodeObjectForKey:#"someArray"] retain];
What kind of objects are you storing in the array? Make sure that all objects stored in the array implement the NSCoding protocol.
NSKeyedArchiver/Unarchiver should encode and decode NSArrays and NSDictionaries with no problem. If your packet class you've created implements the NSCoding protocol, you need to explicitly call [encodeObject:myNsArrayforKey:#"stringsArray"] in your -encodeWithCoder: method (assuming that myNsArray is the name of the instance variable in your packet object you want to encode). But then the archiver and NSArray should take care of the rest of it. If you're doing this, it would be helpful to hear more about the layout of your classes and who's calling who when encoding/decoding.