Reputation: 1954
I find it hard to write/read array of custom objects. In my my app, Contact class has a NSDictionary as property and this dictionary has array as objects for keys. I serialize/deserialize my objects with NSCoder and NSKeyedArchiever and even tried NSPropertyList serialization. I always get errors when serializing as soon as it starts to serialize NSDictionary. Here is my code and I didn't really find a general answer regarding how to serialize custom objects with complex structure?
//Contact.m
//phoneNumbers is a NSDictionary
#pragma mark Encoding/Decoding
-(void)encodeWithCoder:(NSCoder *)aCoder
{
NSLog(@"Encoding");
[aCoder encodeObject:self.firstName forKey:@"firstName"];
NSLog(@"First name encoded");
[aCoder encodeObject:self.lastName forKey:@"lastName"];
NSLog(@"Last name encoded");
[aCoder encodeInt:self.age forKey:@"age"];
NSLog(@"Age encoded");
NSString *errorStr;
NSData *dataRep = [NSPropertyListSerialization dataFromPropertyList:self.phoneNumbers
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorStr];
NSLog(@"Data class %@", [dataRep class]);
if(!dataRep)
{
NSLog(@"Error encoding %@", errorStr);
}
[aCoder encodeObject:dataRep forKey:@"phones"];
NSLog(@"Encoding finished");
}
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
[self setFirstName:[coder decodeObjectForKey:@"firstName"]];
[self setLastName:[coder decodeObjectForKey:@"lastName"]];
[self setAge:[coder decodeIntForKey:@"age"]];
NSString *errorStr;
NSData *data=[coder decodeObjectForKey:@"phones"];
NSDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&errorStr];
if(!propertyList)
{
NSLog(@"Error %@", errorStr);
}
[self setPhoneNumbers:propertyList];
}
return self;
}
//Serializing/Deserializing an array of Contact objects:
#pragma mark Import/Export
//Export Contacts to file
-(void)exportContactsToFile
{
BOOL done=[NSKeyedArchiver archiveRootObject:self.contacts toFile:[PathUtility getFilePath:@"phonebook"]];
NSLog(@"Export done: %i", done);
}
//Import Contacts from file
-(void)importContactsFromFile
{
self.contacts = [NSKeyedUnarchiver unarchiveObjectWithFile:[PathUtility getFilePath:@"phonebook"]];
}
Is there a generic good way to serialize/deserialize objects in objective-c? thanks The error I get is: 0objc_msgSend 1 CF_Retain ... that's stack trace, but I get no other errors(
Upvotes: 0
Views: 2347
Reputation: 19164
I have a special class in my proprietary library that automatically reads the list of its properties and use the getter and setter to encode and decode the object. Sorry I can't share the code here but I can at least give you steps by steps how my class works:
First, the class must be implement NSCoding
and NSCopying
protocols.
Inside + (void)initialize
, iterate thru the definitions of the properties of the class using class_copyPropertyList()
, property_getName()
and property_copyAttributeList()
. Refer Objective-C Runtime Programming Guide for details on these functions.
For each property, run thru its attribute list and get the attribute with strncmp(attribute.name, "T", 1) == 0
(yup, it's a c-string in there). Use that attribute value to determine the type of the property. For example, "i" means int
, "I" means unsigned int
, if it starts with a "{" then it's a struct
etc. Refer this page on the Type Encoding.
Store the property name-type pairs inside a NSDictionary
. At the end of properties iteration, store this dictionary inside a static and global NSMutableDictionary
using the class name as the key.
To support auto-encoding, implement - (void)encodeWithCoder:(NSCoder *)aCoder
to iterate thru the property name-type pair, calling the property getter method (usually - (returnType)propertyName
) and encode it inside the coder using appropriate encodeType:
method (e.g. encodeInt:
, encodeFloat:
, encodeObject:
, encodeCGPoint:
etc).
To support auto-decoding, implement - (id)initWithCoder:(NSCoder *)aDecoder
to iterate thru the property name-type pair, decode it from the decoder using appropriate decodeTypeForKey:
method (e.g. decodeIntForKey:
, decodeFloatForKey:
, decodeObjectForKey:
, decodeCGPointForKey:
etc). and call the property setter method (usually - (void)setPropertyName:
).
Implement an instance method that trigger the encoding (luckily I can share this method here ^__^):
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *arc = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[arc encodeRootObject:self];
[arc finishEncoding];
[arc release];
return data;
Once you have the NSData
you can anything with it such as calling writeToFile:atomically:
or even [[NSUserDefaults standardUserDefaults] setObject:[self wrapInNSData] forKey:key]
.
Also, implement a class method that returns a new instance of the object loaded from the file:
NSKeyedUnarchiver *unarc = [[NSKeyedUnarchiver alloc] initForReadingWithData:[NSData dataWithContentsOfFile:dataFilePath]];
MyCoolFileDataStore *data = [unarc decodeObject];
[unarc release];
return data;
Finally, to make another object class supports this auto-encoding-decoding, the class needs to extend the special class.
Sorry, it's a bit long winded, but for my case, the extra trouble that I took to create this class really save a lot of time along the road. Struggle today, breeze through tomorrow ;)
Upvotes: 3
Reputation: 515
You shouldn't need to use NSPropertyListSerialization
for self.phoneNumbers. NSDictionary adheres to the NSCoding
protocol.
So, [aCoder encodeObject:self.phoneNumbers forKey:@"phones"];
should be sufficient.
As long as a class adheres to NSCoding (which nearly all Apple-provided class do), you can just use -encodeObject:forKey:
, since that method will call that object's implementation of -encodeWithCoder:
Upvotes: 3