Can we decode twice in the same session with unarchiver?

In a class, I call the following (edited to simplify, but it matches the real case).

If I do this:

func getData() -> someClass? {

    _ = someURL.startAccessingSecurityScopedResource()  

    if let data = NSData(contentsOf: someURL as URL) {
        do {
            let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data as Data)
            print((unarchiver.decodeObject(of: [NSArray.self, someClass.self /* and few others*/], forKey: oneKey) as? someClass)?.aProperty)

            if let result = unarchiver.decodeObject(of: [NSArray.self, someClass.self /* same other types*/], forKey: oneKey) as? someClass { 
                unarchiver.finishDecoding()
                print("unarchived success")
                return result
            } else {
                unarchiver.finishDecoding() 
                print("unarchiving failed")
                return someClass() 
            }
        }
        catch {
            return nil
        }
}

I get a failure on log : unarchiving failed

But if I comment out the print(unarchiver.decodeObject) - line 8, it works and I get unarchived success

            // print((unarchiver.decodeObject(of: [NSArray.self, someClass.self /* and few others*/], forKey: oneKey) as? someClass)?.aProperty)

However, when I do exactly the same for another class (I've compared line by line to be sure), it works even with the print statement.

What could be happening here ?

Answered by DTS Engineer in 873950022

I’ve found that it’s quicker to resolve archiving issues when we share small working examples. To that end, I took the BorderModel and FrameModel classes from this post and wrote the following code to exercise archiving and unarchiving:

func main() throws {
    let borderModel = BorderModel(showBorder: true)
    let models = [FrameModel(count: 1), FrameModel(count: 2)]
    
    let archiver = NSKeyedArchiver(requiringSecureCoding: true)
    defer { archiver.finishEncoding() }
    archiver.encode(borderModel, forKey: "borderModel")
    archiver.encode(models as NSArray, forKey: "models")
    let archive = archiver.encodedData
    
    let unarchiver = try NSKeyedUnarchiver(forReadingFrom: archive)
    defer { unarchiver.finishDecoding() }
    assert(unarchiver.requiresSecureCoding)
    guard
        let borderModel2 = try unarchiver.decodeTopLevelObject(of: BorderModel.self, forKey: "borderModel"),
        let m = try unarchiver.decodeTopLevelObject(of: [NSArray.self, FrameModel.self], forKey: "models"),
        let models2 = m as? [FrameModel]
    else {
        throw POSIXError(.ENOTTY)       // Need a better error (-:
    }
    
    print(borderModel2.showBorder)      // true
    print(models2.map { $0.count } )    // [1, 2]
}
 
try main()

I tested this with Xcode 26.2 on macOS 26.2 and it ran as expected. And I duplicate the guard statement, so that it decodes everything twice, it continues to work.

Please try this for yourself to confirm that it works. Then look at your code to see what it’s doing differently. And if you can’t figure it out, post a modified version of this snippet that shows the problem.

Share and Enjoy

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

When decoding objects from an archive directly — as opposed to implementing secure coding in init(coder:) — you want to use the ‘top-level object’ APIs. In this case that means the decodeTopLevelObject(of:forKey:) method.

Do you have the same problem if you use that?

Share and Enjoy

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

Thanks for the reply.

I tried to replace

if let result = unarchiver.decodeObject(of: [/* list */], forKey: someKey) as? someClass {

by

 do {
      let result = try unarchiver.decodeTopLevelObject(of: [/* list */], forKey: someKey) as! someClass

It compiles but does not decode: I get a failure on log : unarchiving failed

And with this pattern, I do not know how to test the "double call" within the print

Is it an error to use unarchiver.decodeObject ? What can be the side effects ?

unarchiver.decodeObject(of: [/* list */], forKey: someKey)
Accepted Answer

I’ve found that it’s quicker to resolve archiving issues when we share small working examples. To that end, I took the BorderModel and FrameModel classes from this post and wrote the following code to exercise archiving and unarchiving:

func main() throws {
    let borderModel = BorderModel(showBorder: true)
    let models = [FrameModel(count: 1), FrameModel(count: 2)]
    
    let archiver = NSKeyedArchiver(requiringSecureCoding: true)
    defer { archiver.finishEncoding() }
    archiver.encode(borderModel, forKey: "borderModel")
    archiver.encode(models as NSArray, forKey: "models")
    let archive = archiver.encodedData
    
    let unarchiver = try NSKeyedUnarchiver(forReadingFrom: archive)
    defer { unarchiver.finishDecoding() }
    assert(unarchiver.requiresSecureCoding)
    guard
        let borderModel2 = try unarchiver.decodeTopLevelObject(of: BorderModel.self, forKey: "borderModel"),
        let m = try unarchiver.decodeTopLevelObject(of: [NSArray.self, FrameModel.self], forKey: "models"),
        let models2 = m as? [FrameModel]
    else {
        throw POSIXError(.ENOTTY)       // Need a better error (-:
    }
    
    print(borderModel2.showBorder)      // true
    print(models2.map { $0.count } )    // [1, 2]
}
 
try main()

I tested this with Xcode 26.2 on macOS 26.2 and it ran as expected. And I duplicate the guard statement, so that it decodes everything twice, it continues to work.

Please try this for yourself to confirm that it works. Then look at your code to see what it’s doing differently. And if you can’t figure it out, post a modified version of this snippet that shows the problem.

Share and Enjoy

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

Can we decode twice in the same session with unarchiver?
 
 
Q