How to replace NSKeyedUnarchiver.unarchiveTopLevelObjectWithData with unarchivedObject when data is a Dictionary

1.9k views Asked by At

iOS 16.4 deprecates unarchiveTopLevelObjectWithData(_:) and should be replaced with unarchivedObject(ofClass:from:).

When you archived a Swift Dictionary like [String: Any], how do you use the newer API to unarchive it?

NSKeyedUnarchiver.unarchivedObject(ofClass: [String: Any].self, from: data) results in a build time error:

Static method 'unarchivedObject(ofClass:from:)' requires that '[String : Any]' conform to 'NSCoding'

//create the data from dictionary
let dictionary: [String: Any] = ["Text": "Hello", "Number": 1, "Array": ["Hello", "World"]]
let data = try! NSKeyedArchiver.archivedData(withRootObject: dictionary, requiringSecureCoding: true)

//and later get the dictionary from the data
let dictionary = try? NSKeyedUnarchiver.unarchivedObject(ofClass: [String: Any].self, from: data) //sorry no can do
3

There are 3 answers

1
HangarRash On BEST ANSWER

Since the Swift dictionary of type [String: Any] doesn't conform to NSCoding, you need to drop back to using NSDictionary. Since you are also using secure coding, you also need to list all types that can be in the dictionary.

You will need to use the unarchivedObject(ofClasses:from:) method so you can list all of the classes.

let dictionary = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSDictionary.self, NSArray.self], from: data) as? NSDictionary

You may also end up needing to add NSString.self and NSNumber.self to the list of classes. Keep adding the classes that appear in the error message until it works.

1
user2819640 On

You can use this dummy object: [NSObject.self] instead of [NSDictionary.self, NSArray.self ...... ]. With this, you cover all NS cases. It worked for me, although purple message appears (Apple always on the safe side. Not bad, but...

-[NSKeyedUnarchiver validateAllowedClass:forKey:]: NSSecureCoding allowed classes list contains [NSObject class], which bypasses security by allowing any Objective-C class to be implicitly decoded. Consider reducing the scope of allowed classes during decoding by listing only the classes you expect to decode, or a more specific base class than NSObject. This will become an error in the future. Allowed class list: {( "'NSObject' (0x219ef2d98) [/usr/lib]" )}

0
Sebastian On

In my case, I was able to solve this issue using the following code (Source):

func loadBookmarks() {
    guard let dataFromFile = try? Data(contentsOf: URL(fileURLWithPath: fullPath.path)) else { return }
    guard let bookmarksFromFile = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [
        NSDictionary.self,
        NSArray.self,
        NSString.self,
        NSURL.self,
        NSData.self
    ], from: dataFromFile) as? [URL: Data]  else {
        print("Failed to unarchive bookmarks.")
        return
    }
    let loadedBookmarks: [URL: Data] = bookmarksFromFile
}