NSCoding with Nested Custom Objects? - iphone

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"];
}

Related

Save subclass of SKShapeNode using NSCoding

I have a subclass of SKShapenode with 2 extra attributes
#property(assign)float size;
#property(assign)float weight;
But when I save a NSMutableArray, with core data, containing some subclasses of my node, the information about size + weight is lost.
newLevel.tractorObjects = [NSKeyedArchiver archivedDataWithRootObject:arrayTractorObjects];
How can I solve this problem?
Since you appear to be using NSCoding to encode the nodes, you need to implement NSCoding in your subclass. Right now SKShapeNode is doing all of the encoding, but it doesn't know about your subclass attributes. To get your attributes encoded, you need to implement encodeWithCoder and initWithCoder. Both of these should call super's implementation so that SKShapeNode can encode itself, and then add/extract your own attributes.

Subclassing NSMutableDictionary

I am trying to implement a subclass of NSMutableDictionary that returns nil instead of throwing a NSUndefinedKeyException when the key is not present in the Dictionary.
However when I try to add objects to my dictionary I get
[NSMutableDictionary setObject:forKey:]: method only defined for abstract class
NilDictionary.h
#interface NilDictionary : NSMutableDictionary {
}
#end
NilDctionary.m
#implementation NilDictionary
- (id)valueForUndefinedKey:(NSString *)key {
return nil;
}
#end
Do I really have to implement all the methods from NSMutableDictionary again in my subclass or is there some other class I should be subclassing?
Clarification: My original problem came down to me not being able to read the documentation properly.
If you need to subclass NSMutableDictionary check out the correct answer. If you want a dictionary that returns nil when your key is not present, NSMutableDictionary does that already.
NSMutableDictionary Class Reference says:
In a subclass, you must override both of its primitive methods:
1. setObject:forKey:
2. removeObjectForKey:
You must also override the primitive methods of the NSDictionary class.
NSDictionary Class Reference says:
If you do need to subclass NSDictionary, you need to take into account that is represented by a Class cluster—there are therefore several primitive methods upon which the methods are conceptually based:
1. count
2. objectForKey:
3. keyEnumerator
4. initWithObjects:forKeys:count:
In a subclass, you must override all these methods.
NSDictionary’s other methods operate by invoking one or more of these primitives. The non-primitive methods provide convenient ways of accessing multiple entries at once.
It seems that you need to override all these six methods to make your NSMutableDictionary subclass work perfect.
Here's your problem. NSDictionary (and its mutable counterpart) is part of a class cluster (read more about them here, under the 'Class Cluster' heading), and should not be subclassed because it causes problems such as what you've mentioned (read the subclassing notes in the NSDictionary Class Reference). Whatever it is you need to do, you're going to have a way to extend the classes you want to use in order to do what you want to do. For instance, the above code can easily be placed in a category (read more about categories here).
Are you sure you are not getting the exception when passing in "nil" for a KEY (not a value)?

How to save my own object with NSKeyedArchiver?

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];

[iPhone]Objective C, objects which do not conform to NSCoding. How to write them to a file

I am using an Objective c class, a subclass of NSObject.
This class cannot be modified. I have an instance of this class that I wish to write to a file which can be retrieved and later reinstate. The object does not conform to NSCoding.
To sum up, I need to save an instance of a class to a file which can be retrieved later, without using any of the NSCoding methods such as NSKeyedArchiving encodeWithCoder ...
Using them returns this...
NSInvalidArgumentException ...encodeWithCoder:]
unrecognised selector sent to instance...
Is there any other way I can store this object for later use
Thank you
p.s The class is a UIEvent
Can you subclass the class and implement the NSCoding protocol in the subclass?
Depending on what the object has as members, you could create a dictionary, and set each member's value as a key in the dictionary and then write that to file.
However, I don't understand why you can't subclass the object and implement the NSCoding protocol, as Jaanus suggested?

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