'unarchiveTopLevelObjectWithData' was deprecated Use unarchivedObject(ofClass:from:) instead

I have a macOS application with SwiftUI. I am saving a dictionary containing two custom classes with NSSavePanel. That's not a problem.

import SwiftUI

struct ContentView: View {
	var body: some View {
		...
	}

	private func savePanel() -> URL? {
		let savePanel = NSSavePanel()
		savePanel.allowedContentTypes = [.myCustomeFileType]
		savePanel.canCreateDirectories = true
		savePanel.isExtensionHidden = false
		savePanel.title = "Saving..."
		savePanel.message = "Please select a path where to save a file."
		savePanel.nameFieldStringValue = "Untitled"
		return savePanel.runModal() == .OK ? savePanel.url : nil
	}

	private func fileSaveAs() {
		if let url = savePanel() {
			let models = colorViewModel.frameModels
			let borderModel = BorderModel(showBorder: true, colorIndex: 6, borderWeightIndex: 8)
			let dict = ["FrameModelArray": models, "BorderModel": borderModel] as [String : Any]
			NSKeyedArchiver.setClassName("FrameModel", for: FrameModel.self)
			NSKeyedArchiver.setClassName("BorderModel", for: BorderModel.self)

			do {
				let data = try NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false)
				try data.write(to: url, options: .atomic)
			} catch {
				print("Errrrrr \(error.localizedDescription)")
			}
		}
	}
}

So my custom classes are FrameModel, BorderModel.

I can unarchive a saved file with a deprecated type method as follows.

private func fileOpen() {
	if let url = openPanel() {
		do {
			NSKeyedUnarchiver.setClass(FrameModel.self, forClassName: "FrameModel")
			NSKeyedUnarchiver.setClass(BorderModel.self, forClassName: "BorderModel")
			let data = try Data(contentsOf: url)
			if let someData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) {
				if let dict = someData as? [String : Any] {
					if let frameModels = dict["FrameModelArray"] as? [FrameModel] {
						print("[FrameModel] read...")
					}
					if let borderModel = dict["BorderModel"] as? BorderModel {
						print("BorderModel read...")
					}
				}
			}
		} catch {
			print("Errrrrr \(error.localizedDescription)")
		}
	}
}

If I use unarchivedObject(ofClasses:from:), I can't unarchive my file. What am I doing wrong? Thanks.

private func fileOpen() {
	if let url = openPanel() {
		do {
			NSKeyedUnarchiver.setClass(FrameModel.self, forClassName: "FrameModel")
			NSKeyedUnarchiver.setClass(BorderModel.self, forClassName: "BorderModel")
			let data = try Data(contentsOf: url)
			if let dictionary = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [FrameModel.self, BorderModel.self], from: data) as? NSDictionary {
				print("Being read...")
			} else {
				print("Not read...")
			}
		} catch {
			print("Errrrrr \(error.localizedDescription)")
		}
	}
}
Answered by DTS Engineer in 764405022

Is there a reason you’re doing this with a keyed archive? Keyed archives are very Objective-C aligned, which makes them tricky to use from Swift. In Swift it’s usually much easier to lean into Codable.

Anyway, with regards your secure coding problem, you need to list all of the classes you expect to see in the archive. So:

try NSKeyedUnarchiver.unarchivedObject(ofClasses: [
    NSDictionary.self,
    NSArray.self,
    NSString.self,
    FrameModel.self,
    BorderModel.self,
], from: data)

Pasted in below is a small test project that successfully encodes and decodes something like your example.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


import Foundation

class BorderModel: NSObject, NSSecureCoding {
    class var supportsSecureCoding: Bool { true }
    
    var showBorder: Bool
    
    init(showBorder: Bool) {
        self.showBorder = showBorder
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(self.showBorder, forKey: "border")
    }
    
    required init?(coder: NSCoder) {
        self.showBorder = coder.decodeBool(forKey: "border")
    }
}

class FrameModel: NSObject, NSSecureCoding {
    class var supportsSecureCoding: Bool { true }

    var count: Int
    
    init(count: Int) {
        self.count = count
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(self.count, forKey: "count")
    }
    
    required init?(coder: NSCoder) {
        self.count = coder.decodeInteger(forKey: "count")
    }
}

func main() {
    let models = [FrameModel(count: 1), FrameModel(count: 2)]
    let borderModel = BorderModel(showBorder: true)
    let dict = ["FrameModelArray": models, "BorderModel": borderModel] as [String : Any]
    let data = try! NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: true)
    let dict2 = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [
        NSDictionary.self,
        NSArray.self,
        NSString.self,
        FrameModel.self,
        BorderModel.self,
    ], from: data)
    print(dict2)
}

main()
Accepted Answer

Is there a reason you’re doing this with a keyed archive? Keyed archives are very Objective-C aligned, which makes them tricky to use from Swift. In Swift it’s usually much easier to lean into Codable.

Anyway, with regards your secure coding problem, you need to list all of the classes you expect to see in the archive. So:

try NSKeyedUnarchiver.unarchivedObject(ofClasses: [
    NSDictionary.self,
    NSArray.self,
    NSString.self,
    FrameModel.self,
    BorderModel.self,
], from: data)

Pasted in below is a small test project that successfully encodes and decodes something like your example.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


import Foundation

class BorderModel: NSObject, NSSecureCoding {
    class var supportsSecureCoding: Bool { true }
    
    var showBorder: Bool
    
    init(showBorder: Bool) {
        self.showBorder = showBorder
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(self.showBorder, forKey: "border")
    }
    
    required init?(coder: NSCoder) {
        self.showBorder = coder.decodeBool(forKey: "border")
    }
}

class FrameModel: NSObject, NSSecureCoding {
    class var supportsSecureCoding: Bool { true }

    var count: Int
    
    init(count: Int) {
        self.count = count
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(self.count, forKey: "count")
    }
    
    required init?(coder: NSCoder) {
        self.count = coder.decodeInteger(forKey: "count")
    }
}

func main() {
    let models = [FrameModel(count: 1), FrameModel(count: 2)]
    let borderModel = BorderModel(showBorder: true)
    let dict = ["FrameModelArray": models, "BorderModel": borderModel] as [String : Any]
    let data = try! NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: true)
    let dict2 = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [
        NSDictionary.self,
        NSArray.self,
        NSString.self,
        FrameModel.self,
        BorderModel.self,
    ], from: data)
    print(dict2)
}

main()

I've changed

class FrameModel: NSObject, NSCoding {

}

into

class FrameModel: NSObject, NSSecureCoding {
	class var supportsSecureCoding: Bool { true }

}

Muchos thankos, eskimo.

'unarchiveTopLevelObjectWithData' was deprecated Use unarchivedObject(ofClass:from:) instead
 
 
Q