Search code examples
objective-cnsmutabledictionarynscodingnskeyedarchivernskeyedunarchiver

Custom object inheriting NSMutableDictionary and serialized with NSKeyedArchiver can't be deserialized


Class is not unarchived properly. See code example below.

@interface A : NSMutableDictionary 
@end

@implementation A
- (void)encodeWithCoder:(NSCoder *)aCoder
{

}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {

    }   
    return self;
}
@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    A *a = [[A alloc] init];

    NSMutableDictionary *dict = [NSMutableDictionary new];
    dict[@"some"] = a;

    NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:dict];

    dict = [NSKeyedUnarchiver unarchiveObjectWithData:archive];

    NSLog(@"%@",dict);
}

After unarchiveObjectWithData invocation the dict contains pair key @"some" but object is NSMutableDictionary not an A class. And while unarchiveObjectWithData invocation no initWithCoder invocation gets occurred. So, how make it code works? Why class inheriting NSMutableDictionary is not deserialized?


Solution

  • This method:

    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
    }
    

    Contains the instructions for the object to encode itself, which is to say, "don't do anything". What it should say is:

    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        // perform the inherited behavior or encoding myself
        [super encodeWithCoder:encoder];
    }
    

    EDIT again, This test class subclasses an NSMutableDictionary in the most trivial way: by hiding a mutable dictionary instance in it's implementation and providing the primitive methods (PLUS encodeWithCoder)

    #import "MyDict.h"
    
    @interface MyDict ()
    @property(strong) NSMutableDictionary *internalDictionary;
    @end
    
    @implementation MyDict
    
    // changed to the default init for the "new" constructor
    - (id)init {
        self = [super init];
        if (self) {
            _internalDictionary = [[NSMutableDictionary alloc] init];
        }
        return self;
    }
    
    - (NSUInteger)count {
        return [self.internalDictionary count];
    }
    
    - (id)objectForKey:(id)aKey {
        return [self.internalDictionary objectForKey:aKey];
    }
    
    - (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey {
        return [self.internalDictionary setObject:anObject forKey:aKey];
    }
    
    - (void)removeObjectForKey:(id)aKey {
        return [self.internalDictionary removeObjectForKey:aKey];
    }
    
    - (NSEnumerator *)keyEnumerator {
        return [self.internalDictionary keyEnumerator];
    }
    
    // added encoding of the internal representation
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        [super encodeWithCoder:aCoder];
        [aCoder encodeObject:_internalDictionary forKey:@"internalDictionary"];
    }
    
    // added decoding of the internal representation
    - (id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            _internalDictionary = [aDecoder decodeObjectForKey:@"internalDictionary"];
        }
        return self;
    }
    
    - (Class)classForCoder {
        return self.class;
    }
    
    @end
    

    EDIT again, this time with exactly your test:

    MyDict *a = [MyDict new];
    a[@"hi"] = @"there";
    
    NSMutableDictionary *dict = [NSMutableDictionary new];
    dict[@"some"] = a;
    NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:dict];
    dict = [NSKeyedUnarchiver unarchiveObjectWithData:archive];
    NSLog(@"%@", dict);
    

    Logs...

    { some = { hi = there; }; }