NSKeyedUnarchiver decodeObjectOfClasses failed

I tried to read an Object :

-(NSMutableDictionary*) readMyObject:(NSData*)data;
{
    NSError * error;
    Class class = [NSMutableDictionary class];
    NSMutableDictionary * dict;
    dict = [NSKeyedUnarchiver unarchivedObjectOfClass:class
                                           fromData:data error:&error];
    return dict;

the result was nil.

I searched by Developer for a solution and found one :

{
//    NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] init];
    [unarchiver decodeObjectOfClasses:
        [[NSSet alloc]initWithArray:
                 @[[NSDictionary class],
                        [NSMutableDictionary class],
                        [NSArray class],
                        [NSMutableArray class],
                        [NSString class], [NSNumber class]]]
                   forKey:NSKeyedArchiveRootObjectKey];
   
    [unarchiver finishDecoding];
}

The first line was from me and it crashed the project.

I assume there is an easy answer, not for me.🥲 Uwe

Accepted Reply

There are two cases to consider here. First, consider a custom object like this:

@interface MyObject : NSObject <NSSecureCoding>

@property (nonatomic, copy, readwrite, nullable) NSString * myProperty;

@end

You implement secure coding support like this:

+ (BOOL)supportsSecureCoding {
    return YES;
}

- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { 
    self = [super init];
    if (self != nil) {
        self->_myProperty = [[coder decodeObjectOfClass:[NSString class] forKey:@"myProperty"] copy];
    }
    return self;
}

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

And then encode and decode like this:

MyObject * obj = [[MyObject alloc] init];
obj.myProperty = @"Hello Cruel World!";
NSData * archive = [NSKeyedArchiver archivedDataWithRootObject:obj requiringSecureCoding:true error: nil];
MyObject * obj2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[MyObject class] fromData:archive error:nil];

The second case relates to collections, where you have to authorise the decoding of both the container and the items within the container. I have an example of this here, albeit one in Swift. Fortunately the take-home point — the need to use +unarchivedObjectOfClasses:fromData:error: — is pretty clear.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

There are two cases to consider here. First, consider a custom object like this:

@interface MyObject : NSObject <NSSecureCoding>

@property (nonatomic, copy, readwrite, nullable) NSString * myProperty;

@end

You implement secure coding support like this:

+ (BOOL)supportsSecureCoding {
    return YES;
}

- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { 
    self = [super init];
    if (self != nil) {
        self->_myProperty = [[coder decodeObjectOfClass:[NSString class] forKey:@"myProperty"] copy];
    }
    return self;
}

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

And then encode and decode like this:

MyObject * obj = [[MyObject alloc] init];
obj.myProperty = @"Hello Cruel World!";
NSData * archive = [NSKeyedArchiver archivedDataWithRootObject:obj requiringSecureCoding:true error: nil];
MyObject * obj2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[MyObject class] fromData:archive error:nil];

The second case relates to collections, where you have to authorise the decoding of both the container and the items within the container. I have an example of this here, albeit one in Swift. Fortunately the take-home point — the need to use +unarchivedObjectOfClasses:fromData:error: — is pretty clear.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hello Quinn,

I tried to implement your proposal. Need I to implement secure coding support as in the example? According to the example implementing 'secure coding support' requires about a dozen lines of code.

What I have done: Deprecated code tmp = [NSKeyedArchiver archivedDataWithRootObject:savedSettingsArray]; settingsArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmp]; (tmp is NSData, savedSettingsArray + settingsArray are NSMutableArray)

I tried this: tmp = [NSKeyedArchiver archivedDataWithRootObject:savedSettingsArray requiringSecureCoding:true error: nil]; settingsArray = [NSKeyedUnarchiver unarchivedObjectOfClass: [NSMutableArray class] fromData:tmp error:nil];

Result: it works. Question: My app comprises about 500 methods, many of them contain pairs of deprecated NSKeyedArchiver+NSKeyedUnarchiver. I can't change these all at once. The chance of coding errors is great. So, I try to make changes bit by bit. How long does the deprecated code works?

Although the corrected lines of code work the Console shows the feedback:

*** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSString' (0x1f30548c8) [/System/Library/Frameworks/Foundation.framework]' for key 'NS.objects', even though it was not explicitly included in the client allowed classes set: '{( "'NSMutableArray' (0x1f304d690) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future. BOOL _NSPersistentUIDeleteItemAtFileURL(NSURL *const __strong) Failed to stat item: file:///Users/gefa/Library/Containers/ch.psys.Observing-Las/Data/Library/Saved%20Application%20State/ch.psys.Observing-Las.savedState/restorecount.plist

I am unsure about this feedback. Need I implement 'secure coding support' in future or is this done automatically?

Best regards, Gerhard

Hello Quinn, I tried to implement your proposal. Need I to implement secure coding support as in the example? According to the example implementing 'secure coding support' requires about a dozen lines of code. What I have done: Deprecated code tmp = [NSKeyedArchiver archivedDataWithRootObject:savedSettingsArray]; settingsArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmp]; (tmp is NSData, savedSettingsArray + settingsArray are NSMutableArray) I tried this: tmp = [NSKeyedArchiver archivedDataWithRootObject:savedSettingsArray requiringSecureCoding:true error: nil]; settingsArray = [NSKeyedUnarchiver unarchivedObjectOfClass: [NSMutableArray class] fromData:tmp error:nil]; Result: it works. Question: My app comprises about 500 methods, many of them contain pairs of deprecated NSKeyedArchiver+NSKeyedUnarchiver. I can't change these all at once. The chance of coding errors is great. So, I try to make changes bit by bit. How long does the deprecated code works? Although the corrected lines of code work the Console shows the feedback: *** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSString' (0x1f30548c8) [/System/Library/Frameworks/Foundation.framework]' for key 'NS.objects', even though it was not explicitly included in the client allowed classes set: '{( "'NSMutableArray' (0x1f304d690) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future. BOOL _NSPersistentUIDeleteItemAtFileURL(NSURL *const __strong) Failed to stat item: file:///Users/gefa/Library/Containers/ch.psys.Observing-Las/Data/Library/Saved%20Application%20State/ch.psys.Observing-Las.savedState/restorecount.plist I am unsure about this feedback. Need I implement 'secure coding support' in future or is this done automatically? Best regards, Gerhard

It would help if you formatted your code using code style; right now it’s very hard to read. See Quinn’s Top Ten DevForums Tips for advice on how to do that.

In terms of the actual code, you wrote:

savedSettingsArray + settingsArray are NSMutableArray

What are the array elements?

My app comprises about 500 methods, many of them contain pairs of deprecated

I think you should take this opportunity to factor out the common code so that you only have one copy of your archiving code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hello Quinn,

Your question:

"What are the array elements? My app comprises about 500 methods, many of them contain pairs of deprecated…" -> Elements are NSStrings, NSArrays, NSMutableArrays, NSNumbers, NSSets, NSMutableSets

I don't understand what you mean by "I think you should take this opportunity to factor out the common code so that you only have one copy of your archiving code." What do you mean by "common code"? Do you mean "deprecated code"? "… only have one copy of your archiving code"?

As already said in my previous reply: Your proposal works. I need to do a big job to change them all bit by bit. What is irritating is this Console feedback:

*** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSString' (0x1f30548c8) [/System/Library/Frameworks/Foundation.framework]' for key 'NS.objects', even though it was not explicitly included in the client allowed classes set: '{( "'NSMutableArray' (0x1f304d690) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future. BOOL _NSPersistentUIDeleteItemAtFileURL(NSURL *const __strong) Failed to stat item: file:///Users/gefa/Library/Containers/ch.psys.Observing-Las/Data/Library/Saved%20Application%20State/ch.psys.Observing-Las.savedState/restorecount.plist

Does this any harm to my code? Can I ignore it?

Best regards, Gerhard

Can I ignore it?

For the moment: Yes. Ultimately: No. The warning says “This will be disallowed in the future.” and it would be unwise to ignore that.

Elements are NSString, NSArray, NSMutableArray, NSNumber, NSSet, NSMutableSet

All of those support secure coding out of the box, so you just need to declare which types you expect to find in your graph. Do that by calling +unarchivedObjectOfClasses:fromData:error:.

For example, this method works but logs a bunch of warnings:

- (void)testNG {
    NSLog(@"-testNG");
    NSArray * aIn = @[ @1, @"one", @[ @2, @"two" ] ];
    NSData * d = [NSKeyedArchiver archivedDataWithRootObject:aIn requiringSecureCoding:YES error:nil];
    NSArray * aOut = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:d error:nil];
    NSLog(@"%@", aOut);
}

OTOH, this method doesn’t:

- (void)testOK {
    NSLog(@"-testOK");
    NSArray * aIn = @[ @1, @"one", @[ @2, @"two" ] ];
    NSData * d = [NSKeyedArchiver archivedDataWithRootObject:aIn requiringSecureCoding:YES error:nil];
    NSSet * allowed = [NSSet setWithArray:@[
        [NSArray class], [NSString class], [NSNumber class]
    ]];
    NSArray * aOut = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowed fromData:d error:nil];
    if ( ![aOut isKindOfClass:[NSArray class]]) {
        … handle this failure …
    }
    NSLog(@"%@", aOut);
}

Keep in mind that the top-level object can be any of types listed in the allowed parameter, so you have to check the type of the returned object.

I don't understand what you mean by "I think you should take this opportunity to factor out the common code so that you only have one copy of your archiving code." What do you mean by "common code"?

IMO you shouldn’t have lots of different routines that call NSKeyed{Un,}Archiver. Rather, change your “500 methods” to use an abstraction layer and have the routines in that abstraction layer be the only code that uses keyed archiving directly. That way fixing your 500 methods is easy — you remove a bunch of keyed archiving code and replace it with a call to your abstraction layer — and then all this weird keyed archiving stuff is done in one place.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"