Search code examples
nscodingnssecurecoding

NSSecureCoding returns nil for properly saved object


i am on macOS 10.15 (bigSUr), XCode 12, objective-c not ios.

I have document based app. It has a simple object "SHGlobalAppData" (NSObject) that contains a property object of a custom class "SHSetupDataModel" (NSObject).

When loading, initWithCoder returns nil for a saved value. Why?

This is the implementation:

I use NSSecureCoding, therefore both SHSetupDataModel and SHGlobalAppData have included the appropriate class method

+ (BOOL)supportsSecureCoding { return YES;}

Saving is done within NSDocument with secure coding

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
    
    NSKeyedArchiver* archiver=[[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
    [archiver encodeObject:self.appData forKey:@"appData"]; // SHSetupDataModel is a property of appData object
    //[...]
}

How saving is done

When it comes to saving, this is the code for SHGlobalAppData

- (void)encodeWithCoder:(NSCoder *)coder {

    // Other properties here

    if (_setupData){
        // Tests
        NSLog(@"%@",[_setupData className]); // returns "SHSetupDataModel"
        BOOL test = [_setupData isKindOfClass:[SHSetupDataModel class]]; // returns TRUE

        [coder encodeObject:_setupData forKey:@"setupData"];
    }
}

The above saving runs through smoothly. Tests are fine.

How loading is done

Now when loading the saved file, the following NSDocument method is invoked:

- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError *__autoreleasing  _Nullable *)outError {

    NSData* data = [[NSData alloc] initWithContentsOfURL:url];
    NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:outError];
    [unarchiver setRequiresSecureCoding:YES];

    // Load appData
    SHGlobalAppData* appData = [unarchiver decodeObjectOfClass:[SHGlobalAppData class] forKey:@"appData"];
    [unarchiver finishDecoding];
    // [...]
}

This invokes the initWithCoder method form SHGlobalAppData - where i get a nil result

- (id)initWithCoder:(NSCoder *)coder {

    self = [super initWithCoder:coder];
    
    if (self) {
        
        if ([coder containsValueForKey:@"setupData"]){
            _setupData = [coder decodeObjectOfClass:[SHSetupDataModel class] forKey:@"setupData"];   // <---- This is nil. Why?              
        }
        // [...]
    }
}

Can anyone please help me why this is returning nil? Or lead me towards a more effective debugging?


Solution

  • The data model itself contained a NSDictionary that was not properly decoded. The solution was to analyze the outError and then step by step work through initWithCoder methods. Finally I had to pass multiple classes in decodeObjectWithClasses:ofKey: