NSSecureCoding trouble with collections of custom class
Asked Answered
P

3

17

I am having trouble with adopting NSSecureCoding. I encode an array containing objects of my custom class, which adopts NSSecureCoding properly. When I decode it, passing the class NSArray (which is the class of the object I encoded), it throws an exception. However, when do the exact same thing with an array of strings, it works fine. I fail to see what is the difference between my class and NSString.

#import <Foundation/Foundation.h>

@interface Foo : NSObject <NSSecureCoding>
@end
@implementation Foo
- (id)initWithCoder:(NSCoder *)aDecoder {
  return [super init];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
}
+ (BOOL)supportsSecureCoding {
  return YES;
}
@end

int main() {
  @autoreleasepool {

    NSMutableData* data = [[NSMutableData alloc] init];
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:@[[Foo new]] forKey:@"foo"];
    [archiver encodeObject:@[@"bar"] forKey:@"bar"];
    [archiver finishEncoding];

    NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    unarchiver.requiresSecureCoding = YES;
    // throws exception: 'value for key 'NS.objects' was of unexpected class 'Foo'. Allowed classes are '{( NSArray )}'.'
    [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"foo"];
    // but this line works fine:
    [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"bar"];
    [unarchiver finishDecoding];

  }
  return 0;
}
Plebeian answered 24/6, 2014 at 0:49 Comment(1)
Does this answer your question? Error of Unexpected class while encode/decode an NSArray with NSSecureCodingHatchel
B
17

My solution was to use decodeObjectOfClasses:forKey:

In Swift:

if let data = defaults.objectForKey(FinderSyncKey) as? NSData
    let unArchiver = NSKeyedUnarchiver(forReadingWithData: data)
    unArchiver.setRequiresSecureCoding(true)
     //This line is most likely not needed, I was decoding the same object across modules
    unArchiver.setClass(CustomClass.classForCoder(), forClassName: "parentModule.CustomClass")
    let allowedClasses = NSSet(objects: NSArray.classForCoder(),CustomClass.classForCoder())
    if let unarchived = unArchiver.decodeObjectOfClasses(allowedClasses, forKey:NSKeyedArchiveRootObjectKey) as?  [CustomClass]{
        return unarchived

    }    
}

In Objective-C it would be something like:

[unArchiver decodeObjectOfClasses:allowedClasses forKey:NSKeyedArchiveRootObjectKey]

The change in decode object to decode objects solved the above exception for me.

Bulb answered 10/11, 2014 at 3:13 Comment(5)
Thanks for this! My Objective-C version looks like: [coder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [MyCustomClass class], nil] forKey:key]; Very poor documentation on Apple's part, but works like a charm!Riser
Brilliant solution. Thanks.It
Thank you so much.Dead
Thank you, you saved my rear!Antilepton
Thank you! Really helped fixing a warning for iOS 15.Surmount
M
8

I was really struggling with this for an hour in Swift 5.

My situation was I had a Set of custom Solution objects:

var resultsSet = Set<Solution>()

Which conformed to Secure Coding:

static var supportsSecureCoding: Bool{ get{ return true } }

Their container object encoded them as an NSSet, because of Secure Coding:

aCoder.encode(resultsSet as NSSet, forKey: "resultsSet")

But I always got a complier error during decoding:

if let decodedResultsSet = aDecoder.decodeObject(of: NSSet.self, forKey: "resultsSet"){
    resultsSet = decodedResultsSet as! Set<Solution>
}

Error:

2020-02-11 22:35:06.555015+1300 Exception occurred restoring state value for key 'NS.objects' was of unexpected class 'App.Solution'. Allowed classes are '{( NSSet )}'. 2020-02-11 22:35:06.564758+1300 *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'value for key 'NS.objects' was of unexpected class 'App.Solution'. Allowed classes are '{( NSSet )}'.'

If I changed the decodeObject(ofClass: to Solution:

if let decodedResultsSet = aDecoder.decodeObject(of: Solution.self, forKey: "resultsSet"){
    resultsSet = decodedResultsSet as! Set<Solution>
}

I get the error:

2020-02-11 22:33:46.924580+1300 Exception occurred restoring state value for key 'resultsSet' was of unexpected class 'NSSet'. Allowed classes are '{( app.Solution )}'. 2020-02-11 22:33:46.933812+1300 *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'value for key 'resultsSet' was of unexpected class 'NSSet'. Allowed classes are '{( app.Solution )}'.'

The answer, which is obvious now, but I could find it anywhere was to realise the allowed object list is an array, and it needs both NSSet and the custom object: Solution.

if let decodedResultsSet = aDecoder.decodeObject(of: [NSSet.self, Solution.self], forKey: "resultsSet"){
    resultsSet = decodedResultsSet as! Set<Solution>
}
Mareld answered 11/2, 2020 at 10:8 Comment(1)
This saved me. Thank you!Cell
C
0

I had the same problem in Swift with iOS 15 / Xcode 13.2. I solved it by simply adding NSNumber (the offending class listed in the warning) in the decoder.decodeObject of required init(coder decoder: NSCoder) { } (I edited the class names). MyVar contains some numbers.

required init(coder decoder: NSCoder) {
    
    myVar = decoder.decodeObject(of: [MyType.self, NSNumber.self], forKey: theKey) as? [MyVar] ?? []
}
Clairclairaudience answered 2/2, 2022 at 21:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.