NSKeyedArchiving issue

I have a large code that I try to update to change deprecated APIs.

In the former version, I used forWritingWith and forReadingWith

    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: data)
    archiver.encode(myObject, forKey: theKey)
if let data = NSMutableData(contentsOf: anURL) {
  let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
        let myObject = unarchiver.decodeObject(forKey: theKey) as! TheObjectType // <<-- returns the object

That I changed to

    let data = NSMutableData()
    let archiver = NSKeyedArchiver(requiringSecureCoding: true)
    archiver.encode(myObject, forKey: theKey)
 if let data = NSMutableData(contentsOf: anURL) {
        do {
            let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data as Data)
           let myObject = unarchiver.decodeObject(forKey: theKey) as? TheObjectType // <<-- This returns nil

This builds correctly.

But on execution, unarchiver.decodeObject now returns nil.

I have searched extensively to find the cause to no avail.

I may probably change the design to avoid NSKeyedArchiver, but that would be a huge refactoring.

I probably miss something obvious.

Could someone hint at the possible cause ?

Answered by DTS Engineer in 873204022

Hey Claude31, it’s nice for me to be helping you for a change (-:

It’s hard to tell exactly what’s going wrong here without know more about the objects in play. However, in general, when you decode something from a secure keyed archive, you have to tell the system what type you’re expecting. That’s the key factor is what makes it secure.

Over the years I’ve helped a bunch of folks with problems like this. All of these threads have code snippets from me that you should find useful:

If you’re still stuck, distill your problem into a test case that I can run, reply here with that, and I’ll take a deeper look.

Share and Enjoy

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

Hey Claude31, it’s nice for me to be helping you for a change (-:

It’s hard to tell exactly what’s going wrong here without know more about the objects in play. However, in general, when you decode something from a secure keyed archive, you have to tell the system what type you’re expecting. That’s the key factor is what makes it secure.

Over the years I’ve helped a bunch of folks with problems like this. All of these threads have code snippets from me that you should find useful:

If you’re still stuck, distill your problem into a test case that I can run, reply here with that, and I’ll take a deeper look.

Share and Enjoy

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

Hey Quinn, thanks for all the material.

I realize that I have a basic issue in my code.

data is not loaded.

In the previous version, archiver had a connection to data

let archiver = NSKeyedArchiver(forWritingWith: data)

That's no more the case, so data is empty, hence the problems.

    let data = NSMutableData()
    let archiver = NSKeyedArchiver(requiringSecureCoding: true)
    archiver.encode(myObject, forKey: theKey)
    archiver.encode(myObject2, forKey: theKey2)
    archiver.finishEncoding()
    do {
        try data.write(to: an uRL, options: []) // data is empty of course !
    } catch {
     }

So should I replace:

    archiver.encode(myObject, forKey: theKey)
    archiver.encode(myObject2, forKey: theKey2)
    archiver.finishEncoding()
    do {
        try data.write(to: anURL, options: []) // So object and object2 are on file

with

    let data = try! NSKeyedArchiver.archivedData(withRootObject: myObject, requiringSecureCoding: true)
    let data2 = try! NSKeyedArchiver.archivedData(withRootObject: myObject2, requiringSecureCoding: true)
    archiver.finishEncoding()

If so, how do I write object and object2 in a single file properly ? Would I have to include all in a single root class ?

Also, when I compare to https://developer.apple.com/forums/thread/759746,

my required init(coder decoder: NSCoder) is different.

   required init(coder decoder: NSCoder) {
        
        super.init() 
        var1 = decoder.decodeObject(forKey: key) as? [someType]
        var2 = decoder.decodeObject(forKey: key2) as? [someType2]
    }

I do not use decodeObject(of:) as in the reference

            let identifier = decoder.decodeObject(of: NSString.self, forKey: "identifier"),
            let codeDataModels = decoder.decodeArrayOfObjects(ofClass: CodeDataModel.self, forKey: "codeDataModels")

Is it OK ?

Accepted Answer
That's no more the case, so data is empty, hence the problems.

Indeed. The solution is to get the encodedData property:

let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode("Hello Cruel World!" as String, forKey: "greeting")
archiver.finishEncoding()
let data = archiver.encodedData
print(data.count)   // 162

I do not use decodeObject(of:) … Is it OK ?

No. A key goal of secure coding is that you should tell the decoder what type you’re expecting. That allows the decoder to apply its own type checking. This is critical in Objective-C, where it’s easy to confuse types. It’s less critical in Swift, where the compiler is much stricter about type checking, but it’s still the right thing to do. So, whenever you decode, aim to use a decodeXyz(…) method that takes type information.

Share and Enjoy

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

NSKeyedArchiving issue
 
 
Q