Search code examples
iphoneobjective-cnsmutabledictionarynskeyedarchiver

encodeWithCoder is not called in derived class of the NSMutableDictionary


Basically I am using some open source code called OrderedDictionary that is derived from NSMutableDictionary. Then I want to save the ordered dictionary data to NSUserDefaults by adding encode and decode method to the OrderedDictionary class. However, I realized the encode and decode methods are not being called, as a result, the decoded dictionary is no longer ordered. Below is my code:

@interface OrderedDictionary : NSMutableDictionary <NSCopying, NSCoding>
{
    NSMutableDictionary *dictionary;
    NSMutableArray *array;
}

In the implementation file:

/**
 * encode the object
 **/
- (void)encodeWithCoder:(NSCoder *)coder
{
    [super encodeWithCoder:coder];
    [coder encodeObject:dictionary forKey:@"dictionary"];
    [coder encodeObject:array forKey:@"array"];
}

/**
 * decode the object
 */
- (id)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self != nil)
    {
        dictionary = [coder decodeObjectForKey:@"dictionary"];
        array = [coder decodeObjectForKey:@"array"];
    }   
    return self;
}

Quick example code for using this:

dictionary = [[OrderedDictionary alloc] init];
[dictionary setObject:@"one" forKey:@"two"];
[dictionary setObject:@"what" forKey:@"what"];
[dictionary setObject:@"7" forKey:@"7"];
NSLog(@"Final contents of the dictionary: %@", dictionary);

if ([[NSUserDefaults standardUserDefaults] objectForKey:@"myDictionary"] == nil)
{
    [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:dictionary] 
                                          forKey:@"myDictionary"];
}
else
{
    NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];

    NSData *savedDictionary = [currentDefaults objectForKey:@"myDictionary"];

    if (savedDictionary != nil)
    {
        OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData:savedDictionary];
        if (oldData != nil) 
        {
            NSLog(@"Final contents of the retrieved: %@", oldData);

        } 
    }
}

The thing is, the final retrievedDictionary does NOT have the original data order and the encode and decode methods are not called at all.

Thanks for any help in advance! :)


Solution

  • There's an NSObject method called -classForCoder that you need to override in OrderedDictionary. From the docs:

    classForCoder
    Overridden by subclasses to substitute a class other than its own during coding.

    -(Class)classForCoder
    Return Value
    The class to substitute for the receiver's own class during coding.

    Discussion
    This method is invoked by NSCoder. NSObject’s implementation returns the receiver’s class. The private subclasses of a class cluster substitute the name of their public superclass when being archived.

    That last line is the key. So, in OrderedDictionary.m:

    - (Class)classForCoder 
    {  
        return [self class]; // Instead of NSMutableDictionary
    } 
    

    Also, if you're not using ARC, make sure you retain the objects coming back from -decodeObjectForKey. As rob mayoff mentions below, you shouldn't call [super initWithCoder:] (or [super encodeWithCoder:'). I also changed the key strings to avoid collisions.

    - (id)initWithCoder:(NSCoder *)coder
    {
        if (self != nil)
        {
            dictionary = [[coder decodeObjectForKey:@"OrderedDictionary_dictionary"] retain];
            array = [[coder decodeObjectForKey:@"OrderedDictionary_array"] retain];
        }   
        return self;
    }