objective-ccocoaappkit

Appkit Cocoa - NSCoding decoding error - NSCocoaErrorDomain 4864


I'm having some trouble with NSCoding, I have this simple "Grade" class:

@interface Grade : NSObject<NSCoding>

@property (copy) NSString *name;
@property NSInteger grade;

@end

It implements NSCoding as follows:

-(void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeObject:self.name forKey:@"name"];
    [coder encodeInt64:self.grade forKey:@"grade"];
}



- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super init];
    if(self)
    {
        self.name = [coder decodeObjectForKey:@"name"];
        self.grade = [coder decodeInt64ForKey:@"grade"];
    }
    
    return self;
}

I have an NSMutableArray of these objects which I put into an archive like so:

 NSArray<Grades*> *array = [NSArray arrayWithArray:self.tableContents];
 NSData *data = [NSKeyedArchiver archivedDataWithRootObject: array requiringSecureCoding:NO error: nil];
 NSLog(@"archived data: %@", data);

This is working fine but the problem is when I try to extract from this archive:

NSError *error = nil;
NSArray *arrayFromData = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:data error:&error];

I keep getting an error: 'NSCocoaErrorDomain - code: 4864' in my NSError variable and the NSKeyedUnarchiver keeps returning nil.

What am I doing wrong here?


Solution

  • The problem is requiringSecureCoding:NO. You must use secure coding at this point. That means you must make Grade adopt NSSecureCoding, not NSCoding; and you must list all the classes you intend to unarchive in a set at unarchiving time.

    So, Grade is now an NSObject<NSSecureCoding>; I'll fill in a few blanks you omitted from your question:

    @implementation Grade
    
    + (BOOL) supportsSecureCoding { return YES; }
    
    - (instancetype)initWithName:(NSString*)name grade:(NSInteger)grade
    {
        self = [super init];
        if(self) {
            self.name = name;
            self.grade = grade;
        }
        return self;
    }
    
    - (NSString*) description {
        return [NSString stringWithFormat: @"name: %@, grade: %ld", self.name, self.grade];
    }
    
    // And the rest is as you have it:
    
    -(void)encodeWithCoder:(NSCoder *)coder
    {
        [coder encodeObject:self.name forKey:@"name"];
        [coder encodeInt64:self.grade forKey:@"grade"];
    }
    
    - (instancetype)initWithCoder:(NSCoder *)coder
    {
        self = [super init];
        if(self)
        {
            self.name = [coder decodeObjectForKey:@"name"];
            self.grade = [coder decodeInt64ForKey:@"grade"];
        }
    
        return self;
    }
    
    @end
    

    And now, let's make an array of Grade, archive it, and unarchive it:

    NSArray<Grade*> *array = @[
        [[Grade alloc] initWithName: @"Matt" grade: 4],
    ];
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject: array requiringSecureCoding:YES error: nil];
    NSLog(@"archived data: %@", data);
    
    NSError *error = nil;
    NSSet *classesSet = [NSSet setWithObjects:[NSString class], [Grade class], [NSArray class], nil];
    NSArray *arrayFromData = (NSArray*)[NSKeyedUnarchiver unarchivedObjectOfClasses: classesSet fromData: data error: &error];
    
    NSLog(@"array: %@", arrayFromData);
    

    Result:

    archived data: {length = 279 ... }
    array: (
        "name: Matt, grade: 4"
    )
    

    Quod erat demonstrandum.