15

I am using unarchiveObjectWithData to unarchive data from NSUserDefaults and it is working good, but it was deprecated in iOS 12.0. Xcode suggests to use unarchivedObjectOfClass:fromData:error:, but this method does not work.

I have an array of objects of my class to unarchive.

I have tried to use unarchivedObjectOfClass:fromData:error:, also unarchivedObjectOfClasses:fromData:error: with all classes (NSArray, NSString, MYCLASS..)

Works

NSArray *stored = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Does not:

NSArray *stored = [NSKeyedUnarchiver unarchivedObjectOfClass:[MYCLASS class] fromData:data error:&error];

I am receiving ”The data couldn’t be read because it isn’t in the correct format.„

Ion Batîr
  • 171
  • 1
  • 1
  • 8

2 Answers2

25

unarchivedObjectOfClass:fromData:error seems to be pretty undocumented, but I have figured it out. In your case as you are unarchiving an NSArray and assuming the contents of the array are standard classes like NSString then this should work for you:

NSArray *stored = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:data error:&error];

However I was unarchiving a custom object PurchasedSubscription and this also applies if your NSArray contains any custom Classes... Firstly the objectOfClass from the method to unarchive needs to be the class of what you are expecting to be the result.

PurchasedSubscription *purchasedSubscription = [NSKeyedUnarchiver unarchivedObjectOfClass:PurchasedSubscription.class fromData:data error:&error];

Next, your custom class needs to conform to NSSecureCoding so add this to the interface of the class. I assume you already have NSCoding implemented.

@interface PurchasedSubscription : NSObject <NSCoding, NSSecureCoding>

@end

Next your class has to override supportsSecureCoding to confirm it is supported

+ (BOOL)supportsSecureCoding
{
    return YES;
}

Next, in your initWithCoder: method, you need to use decodeObjectOfClass:key: instead of decodeObjectForKey when decoding each property, again setting the Class parameter as the class type of what's being decoded.

- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder
{
    self = [self init];

    if (self)
    {
        ReceiptInfo *receiptInfo = [aDecoder decodeObjectOfClass:[ReceiptInfo class] forKey:@"receiptInfo"];
        return [self initWithReceiptInfo:receiptInfo];
    }

    return self;
}

As you can see here, this class also decodes another custom class ReceiptInfo, so I had to repeat this process with that class to get it all to work.

Now when I use

PurchasedSubscription *purchasedSubscription = [NSKeyedUnarchiver unarchivedObjectOfClass:PurchasedSubscription.class fromData:data error:&error];

it securely decodes the PurchasedSubscription class by securely decoding the ReceiptInfo class as it knows at each step what the class type should be before it decodes it.


A note on the opposite NSEncoding. You need to use the method

archivedDataWithRootObject:requiringSecureCoding:error:

instead of

archivedDataWithRootObject:

With this one, you don't pass in the object class, you pass in the actual object. In my case, I create the object like so

PurchasedSubscription *validSub = [[PurchasedSubscription alloc] initWithReceiptInfo:latestReceipt];

and then encode it like this

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:validSub requiringSecureCoding:YES error:&error];
Darren
  • 10,182
  • 20
  • 95
  • 162
  • 1
    Thank you! This helped me, in particular conforming to NSSecureCoding in addition to NSCoding and overriding supportsSecureCoding. – Sarah Nguyen Aug 02 '19 at 00:06
  • Can also only conform to NSSecureCoding and leave out NSCoding. – Sarah Nguyen Aug 02 '19 at 00:30
  • 1
    Shamelessly sharing this closed dupe question with a newbie-friendly, I reckon answer. :D https://stackoverflow.com/a/58043846/3231194 – Glenn Posadas Sep 21 '19 at 21:01
  • What is this "&error"? Xcode doesn't accept it. –  Jan 12 '21 at 20:13
  • You can use `nil` or if you want to check for any error, declare an NSError variable. `NSError *error;` in the line above it. Then after it you can check if error != nil for an error. – Darren Jan 12 '21 at 20:16
3

I faced up with an issue and follow the @Darren answer, but unarchivedObjectOfClass returns nil ! :(

so i tried a lot of variation and found the only success way is my case:

supportsSecureCoding property must be YES

and

NSArray<MyClassConformedToNSObject_NSCoding_NSSecureCoding *> *arrayOfCustomObjects = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSObject class] fromData:data error:&error];
Eugene
  • 129
  • 1
  • 5