19

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?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Spring
  • 11,333
  • 29
  • 116
  • 185
  • 2
    Try [`unarchiveTopLevelObjectWithData`](https://developer.apple.com/documentation/foundation/nskeyedunarchiver/2919664-unarchivetoplevelobjectwithdata). Better check [this thread](https://stackoverflow.com/q/51487622/6541007). – OOPer May 04 '19 at 12:18

5 Answers5

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.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • tnx! I will try btw I use swifty json and my class at the moment adopts these: class Record : NSObject, NSCoding, Codable – Spring May 04 '19 at 14:52
  • 1
    If your class already adopts `Codable` drop `SwiftyJSON`, `NSCoding` (and maybe even `NSObject)` – vadian May 04 '19 at 14:54
  • drop means I just remove SwiftyJson cocoa pod and things will continue working or I need a lot of refactoring? – Spring May 04 '19 at 14:56
  • 1
    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. – vadian May 04 '19 at 14:57
  • ok i will look into that. Btw is your answer still valid considering I adopt NSCoding, Codable at the moment? – Spring May 04 '19 at 15:09
  • Yes, both protocols look similar but are not related to each other. – vadian May 04 '19 at 15:10
  • Very subtle difference, thanks for pointing out the plural @vadian! – Sebastian Westemeyer Feb 09 '22 at 20:52
4

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

NSKeyedUnarchiver.unarchivedObject(ofClasses: [Record.self], from: archived)
Idan Magled
  • 2,186
  • 1
  • 23
  • 33
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!).

0

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")
}
Anton Plebanovich
  • 1,296
  • 17
  • 17
-2

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 }