1

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?

Sasha Sewerow
  • 117
  • 2
  • 7

1 Answers1

1

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

danh
  • 62,181
  • 10
  • 95
  • 136
  • Sorry, but it doesn't work. First of all, have you checked this? I did try and got crash. – Sasha Sewerow Mar 19 '17 at 20:37
  • What's the crash say? Did you implement all of the NSMutableDictionary primitive methods? And remember you have to implement all of immutable dictionary primitive methods, also. – danh Mar 19 '17 at 20:49
  • @SashaSewerow - added code that demonstrates correctness. – danh Mar 19 '17 at 21:01
  • I checked your code with this: MyDict *a = [MyDict new]; NSMutableDictionary *dict = [NSMutableDictionary new]; dict[@"some"] = a; NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:dict]; dict = [NSKeyedUnarchiver unarchiveObjectWithData:archive]; In result, dict have key value pair, with key is "some" value is some NSMutableDictionary but not a MyDict object. And - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder of MyDict is not invoked – Sasha Sewerow Mar 19 '17 at 21:13
  • Subclassing dictionaries is kind of tricky. Your example used new, and mine uses initWithCapacity (invoked by the convenience constructor `dictionary`) Next, I was just demonstrating the non-crash of calling super in the encode method, so I didn't encode the internal dictionary. Finally, in your test code, you didn't set any value into the mutable subclass. I'll edit to demonstrate your test working. Meantime, it's useful to skim the class docs on subclassing dictionaries. – danh Mar 19 '17 at 21:25
  • In your code is the method (instancetype)initWithCoder:(NSCoder *)aDecoder of MyDict invoked? – Sasha Sewerow Mar 19 '17 at 21:33
  • not near my dev machine anymore. will review this when i get back in ~ an hour. – danh Mar 19 '17 at 21:35
  • I checked, initWithCoder is not invoked. – Sasha Sewerow Mar 19 '17 at 21:37
  • @SashaSewerow, Sorry, one more primitive method...`- (Class)classForCoder { return self.class; }`. Will add this in an edit. Are you sure it's really necessary to subclass a mutable dictionary? If you tell me what you're up to, maybe I can suggest a better design. – danh Mar 19 '17 at 22:43
  • I absolutely agree with you. It is 100% precent not the best architecture. But this code is already exist. And there is task to archive it without conceptual changes. – Sasha Sewerow Mar 20 '17 at 09:54
  • How did you find out that - (Class)classForCoder { return self.class; } message is needed to describe. – Sasha Sewerow Mar 20 '17 at 16:01
  • See the discussion section here: https://developer.apple.com/reference/objectivec/nsobject/1411876-classforcoder?language=objc. NSDictionary is the public abstract class of the dictionary cluster. (It's the complexity of all of those private, concrete subclasses that makes it trickier than usual to subclass). – danh Mar 20 '17 at 16:06