Consider two classes like this:
class CodeDataModel: NSObject, NSSecureCoding {
let data: Data
init(data: Data) {
self.data = data
super.init()
}
…
}
class CodeReadModel: NSObject, NSSecureCoding {
let identifier: String
let codeDataModels: [CodeDataModel]
init(identifier: String, codeDataModels: [CodeDataModel]) {
self.identifier = identifier
self.codeDataModels = codeDataModels
}
…
}
Here’s how you add secure coding support to the first:
class CodeDataModel: NSObject, NSSecureCoding {
…
class var supportsSecureCoding: Bool { true }
required init?(coder: NSCoder) {
guard let data = coder.decodeObject(of: NSData.self, forKey: "data") else {
return nil
}
self.data = data as Data
}
func encode(with coder: NSCoder) {
coder.encode(self.data, forKey: "data")
}
}
Note the following:
-
The initialiser is failable. In your code you’re using a non-failable initialiser. That compiles, due to Swift’s subtyping rules, but it’s not useful because you need a way to return an error.
-
I’m using decodeObject(of:forKey:)
, where I pass in the type. This is a critical aspect of secure coding.
-
I’m using NSData
, because Data
isn’t an object. After I check the result for nil
I cast it to Data
before assigning it to the property.
Now let’s add secure coding support to the second:
class CodeReadModel: NSObject, NSSecureCoding {
…
class var supportsSecureCoding: Bool { true }
required init?(coder decoder: NSCoder) {
guard
let identifier = decoder.decodeObject(of: NSString.self, forKey: "identifier"),
let codeDataModels = decoder.decodeArrayOfObjects(ofClass: CodeDataModel.self, forKey: "codeDataModels")
else {
return nil
}
self.identifier = identifier as String
self.codeDataModels = codeDataModels
}
func encode(with coder: NSCoder) {
coder.encode(identifier, forKey: "identifier")
coder.encode(codeDataModels, forKey: "codeDataModels")
}
}
This is very like the first except I’m using decodeArrayOfObjects(ofClass:forKey:)
to decode the array. This is nice for a number of reasons, not least of which is that it returns a Swift array of the right type.
Finally, here’s the test function to make sure that everything is working:
func test() throws {
let dm1 = CodeDataModel(data: Data("Hello".utf8))
let dm2 = CodeDataModel(data: Data("Cruel".utf8))
let dm3 = CodeDataModel(data: Data("World!".utf8))
let model = CodeReadModel(identifier: "greetings", codeDataModels: [dm1, dm2, dm3])
let archive = try NSKeyedArchiver.archivedData(withRootObject: model, requiringSecureCoding: true)
guard let model2 = try NSKeyedUnarchiver.unarchivedObject(ofClass: CodeReadModel.self, from: archive) else {
fatalError()
}
print(model2)
// -> <Test759746.CodeReadModel: 0x600002506440>
print(model2.identifier)
// -> greetings
print(model2.codeDataModels)
// -> [<Test759746.CodeDataModel: 0x600002505da0>, <Test759746.CodeDataModel: 0x600002506620>, <Test759746.CodeDataModel: 0x600002506640>]
print(model2.codeDataModels.map { String(decoding: $0.data, as: UTF8.self) })
// -> ["Hello", "Cruel", "World!"]
}
All of this was built with Xcode 15.4 and testing on macOS 14.5.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"