How to NSKeyedUnarchiver.unarchiveObject
Asked Answered
R

5

22

I have a working code that works but deprecated:

This part is fine:

 let archived = try? NSKeyedArchiver.archivedData(withRootObject: [defaultRecord] as NSArray, requiringSecureCoding: false)

This is Deprecated:

 let records = NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [Record]

'unarchiveObject(with:)' was deprecated in iOS 12.0: Use +unarchivedObjectOfClass:fromData:error: instead

Sounds simple as it is, I couldn't find a way to use the suggested method without getting errors in every combination I tried.

Any working example of this?

Rabush answered 4/5, 2019 at 11:54 Comment(1)
Try unarchiveTopLevelObjectWithData. Better check this thread. – Pressroom
C
21

The usage of the new API to archive an array is a bit tricky.

You could have figured it out yourself if you wouldn't ignore the errors with try? πŸ˜‰

To be able to decode an array of a custom class with unarchivedObject(ofClass:from: you have to use the plural form unarchivedObject(ofClasses:from: and specify both NSArray(!) and the custom class. Further your class must adopt NSSecureCoding

class Record : NSObject, NSSecureCoding {

   static var supportsSecureCoding: Bool {
        return true
    }

....

do {
    let archived = try NSKeyedArchiver.archivedData(withRootObject: [defaultRecord], requiringSecureCoding: false)

    let records = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, Record.self], from: archived) as! [Record]
    print(records)
} catch { print(error) }

But why do you archive defaultRecord as array at all? If you archive the single object you can leave your class as it is and write

do {
    let archived = try NSKeyedArchiver.archivedData(withRootObject: defaultRecord, requiringSecureCoding: false)

    let record = try NSKeyedUnarchiver.unarchivedObject(ofClass: Record.self, from: archived)
    let records = [record]
    print(records)
} catch { print(error) }

Side note: Consider to serialize the class with Codable. It's swiftier and doesn't require inheritance from NSObject.

Condone answered 4/5, 2019 at 14:47 Comment(7)
tnx! I will try btw I use swifty json and my class at the moment adopts these: class Record : NSObject, NSCoding, Codable – Rabush
If your class already adopts Codable drop SwiftyJSON, NSCoding (and maybe even NSObject) – Condone
drop means I just remove SwiftyJson cocoa pod and things will continue working or I need a lot of refactoring? – Rabush
You have to do some refactoring but it's worth it. Codable is built-in (no dependencies) and is much more efficient than SwiftyJSON. Eventually it will be less code than at the moment. – Condone
ok i will look into that. Btw is your answer still valid considering I adopt NSCoding, Codable at the moment? – Rabush
Yes, both protocols look similar but are not related to each other. – Condone
Very subtle difference, thanks for pointing out the plural @vadian! – Twoedged
R
4

The method above didn't worked for me for unarchiving, I did this and it worked:

NSKeyedUnarchiver.unarchivedObject(ofClasses: [Record.self], from: archived)
Ria answered 25/9, 2020 at 14:19 Comment(0)
T
1

If anyone is interested in the Objective C code, here it is:

// pointer to storage for error message
NSError *error = nil;

// read data from "dataPath" location
NSData *codedData = [[NSData alloc] initWithContentsOfFile:dataPath];

// create a set of root class (the array) and the data object class
NSSet<Class> *classes = [NSSet setWithObjects:NSArray.class, Record.class, nil];

// read using classes, not only single class
NSArray *data = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:codedData error:&error];

Again, this is just a "translation" of the code @vadian correctly pointed out (and saved my day!).

Twoedged answered 9/2, 2022 at 20:59 Comment(0)
C
1

I used this solution to leave logic as is and hide the warning:

func clone<T: UIView>() throws -> T {
    let data = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
    let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
    unarchiver.requiresSecureCoding = false
    return try unarchiver.decodeTopLevelObject(of: T.self, forKey: "root")
}
Crain answered 17/5, 2023 at 11:6 Comment(2)
Thanks. Seems to work fine. How did you determine the key should be "root"? Where can I read more about this? – Buster
I don't remember exactly but I guess if you save data to a file and open it with a text editor the 'root' word will be visible there. I just used the fastest approach here. The other possibility is to use system methods but I can't help much with that – Crain
L
-3

Xcode 14 + iOS16

Sample of code that archives and unarchives the record:

guard
    let archivedRecord = try? NSKeyedArchiver.archivedData(withRootObject: defaultRecord, requiringSecureCoding: false),
    let record = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(archivedRecord) as? Record
else { return nil }
Lickerish answered 20/9, 2022 at 11:27 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.