Strange behavoir when decoding an NSArray via NSSecureCoding
Asked Answered
T

4

10

i spent all afternoon banging my head against the wall trying to figure out why decoding of this class was failing. the class has a property that is an NSArray of Foo objects. Foo conforms to NSSecureCoding, and i have successfully encoded and decoded that class by itself. i was getting an error in initWithCoder: that said failed to decode class Foo. through some experimentation, i discovered that i needed to add [Foo class] to initWithCoder: in order for it to work. maybe this will help someone else who's having the same problem. my question is, why is this necessary? i found no suggestion that this is necessary in apple's documentation.

#import "Foo.h"

@interface MyClass : NSObject <NSSecureCoding>
@property (nonatomic) NSArray *bunchOfFoos;
@end

@implementation MyClass

static NSString *kKeyFoo = @"kKeyFoo";

+ (BOOL) supportsSecureCoding
{
    return YES;
}

- (void) encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:self.bunchOfFoos forKey:kKeyFoo];
}

- (id) initWithCoder:(NSCoder *)decoder
{
    if (self = [super init])
    {
        [Foo class]; // Without this, decoding fails
        _bunchOfFoos = [decoder decodeObjectOfClass:[NSArray class] forKey:kKeyFoo];
    }
    return self;
}

@end
Typewritten answered 25/10, 2013 at 0:32 Comment(3)
I think that for what you are trying to accomplish you need to implement encoding/decoding in the Foo class.Chaps
yes, i stated that Foo conforms to NSSecureCoding, meaning encoding/decoding has been implemented in that class.Typewritten
Does this answer your question? Error of Unexpected class while encode/decode an NSArray with NSSecureCodingArtema
T
2

i think i may have figured this out. without the line [Foo class], there is no reference to the class Foo in this file. because of this, i believe the compiler is optimizing the Foo class out, and then the Foo objects within the array cannot be decoded. having [Foo class] in there prevents this.

Typewritten answered 29/10, 2013 at 20:51 Comment(0)
O
12

For those who are still struggling with this: @Ben H's solution didn't solve my problem. And I keep having the following error message:

Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: >'value for key 'NS.objects' was of unexpected class 'ClassA'. Allowed classes are '{(
NSArray
)}'.'

And finally, I realized that for custom classes. You have to use the following function instead decodeObjectOfClasses:

- (id)decodeObjectOfClasses:(NSSet *)classes forKey:(NSString *)key

And you to pass a NSSet of all possible classes in the NSArray to the function above! I am not sure why @Ben H could solve the issue by simply adding a [Foo class] outside of the function. Maybe it is a compiler issue. But anyway, if his solution doesn't work, try this one as well.

Oarsman answered 30/10, 2014 at 18:26 Comment(1)
@Gomfucius glad to know that it helpsOarsman
C
3

I've just encountered similar issue and that was weird and extremely time consuming. I wanted to test my class to be NSSecureCoded correctly with Specta/Expecta. So I've implemented everything as needed specifying class when decoded. At the end of my trials I got weirdest exception:

value for key 'key' was of unexpected class 'MyClass'. Allowed classes are '{(
    MyClass
)}'.

Test looked something like that:

MyClass *myClassInstance = ...
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *secureEncoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[secureEncoder setRequiresSecureCoding:YES]; // just to ensure things

NSString *key = @"key";
[secureEncoder encodeObject:myClassInstance forKey:key];
[secureEncoder finishEncoding];

NSKeyedUnarchiver *secureDecoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[secureDecoder setRequiresSecureCoding:YES];
MyClass *decodedInstance = [secureDecoder decodeObjectOfClass:[MyClass class] forKey:key]; // exception here
[secureDecoder finishDecoding];

...expect...

While plain NSCoding (requiresSecureCoding = NO) test succeeded, NSSecureCoding tests kept failing. After vast range of trials I found solution for that, just a single line:

[secureDecoder setClass:[MyClass class] forClassName:NSStringFromClass([MyClass class])];

After that all my tests succeeded, objects were created as expected.

I'm not sure why did that happened, my guess would be that class is not visible as Ben H suggested and it uses something like NSClassFromString(@"MyClass"). The above code worked fine in AppDelegate. MyClass was from development pods I'm developing.

Cullen answered 5/3, 2015 at 13:57 Comment(0)
T
2

i think i may have figured this out. without the line [Foo class], there is no reference to the class Foo in this file. because of this, i believe the compiler is optimizing the Foo class out, and then the Foo objects within the array cannot be decoded. having [Foo class] in there prevents this.

Typewritten answered 29/10, 2013 at 20:51 Comment(0)
M
1

Yuchen's answer is/was on the right track but the important thing to know is that the NSSet parameter needs to include the class for the collection in addition to the custom class, like so:

_bunchOfFoos = [decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [Foo class], nil] forKey:kKeyFoo];

At least that's what seems to be working for me at this point...

Mispronounce answered 9/1, 2022 at 20:41 Comment(1)
You can incorporate the decoder's allowedClasses set into the argument for collection types, which is the set originally passed with the original decodeTopLevelObjectOfClasses: method.Sachs

© 2022 - 2024 — McMap. All rights reserved.