Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSSecureCoding trouble with collections of custom class

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;
}
like image 474
user102008 Avatar asked Jun 24 '14 00:06

user102008


2 Answers

You've probably already solved this, but I just hit this and found a solution, and thought I'd leave it here for anyone else that finds this.

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.

like image 72
utahwithak Avatar answered Sep 19 '22 07:09

utahwithak


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

I hope this helps someone.

like image 21
Matt C Avatar answered Sep 22 '22 07:09

Matt C