0

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(

1
  • Solved: the problem was my poor memory management abilities Commented Sep 5, 2012 at 16:09

2 Answers 2

3

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:

Sign up to request clarification or add additional context in comments.

3 Comments

that's not sufficient, I've tried this, it throws strange exceptions... but thanks anyway.. maybe the thing is that I've got NSArrays as objects for keys in my dictionary
What exactly is the error that you are getting? Because it really should sufficient.
Could you edit your question with the exact error output / crash dialog that appears with your code?
3

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:

  1. First, the class must be implement NSCoding and NSCopying protocols.

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

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

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

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

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

  7. 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;
    
  8. Once you have the NSData you can anything with it such as calling writeToFile:atomically: or even [[NSUserDefaults standardUserDefaults] setObject:[self wrapInNSData] forKey:key].

  9. 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;
    
  10. 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 ;)

1 Comment

wow, that's very impressive, thanks a lot! I will need this for production environment, so far I'm experimenting! Yes, it will take a while to implement this mechanism, but int the it will be great, you are right. Thank you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.