NSkeyedArchiver fails when trying to archive subclass objects

So, my first time on this forum, but I have been producing simple apps for some time now.

I have an issue with NSKeyedArchiver archiving an array of SecureCoding objects and objects that inherit from another.

I have a class the contains an array (theArray) of objects (anObject). anObject supports SecureCoding.

NSKeyedArchiver.archivedData(withRootObject: theArray, requiringSecureCoding: true) works great and the data object required is created.

If theArray contains objects that inherit from 'anObject' The NSKeyedArchiver function fails.

Thoughts?

Can you post a small snippet of code that illustrates your issue? Use a code block to make it more readable. See tip 5 here.

To get you started I’ve pasted a small test program below.

Share and Enjoy

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

import Foundation

class MyBase: NSObject, NSSecureCoding {

    override init() {
        super.init()
    }

    class var supportsSecureCoding: Bool { true }
    
    required init?(coder: NSCoder) { }
    
    func encode(with coder: NSCoder) { }
}

class MySub: MyBase {
    override class var supportsSecureCoding: Bool { true }
}

func test() throws {
    let input = [MySub(), MySub()]
    let archive = try NSKeyedArchiver.archivedData(
        withRootObject: input,
        requiringSecureCoding: true
    )
    let outputQ = try NSKeyedUnarchiver.unarchivedObject(
        ofClasses: [NSArray.self, MySub.self],
        from: archive
    )
    guard let output = outputQ as? [MySub] else {
        return
    }
    print(output)
}

try test()

So here is stripped down code for my base object 'Aircraft', and the object that inherits from it - C172RS. (this object adds a few properties and includes some different behaviours)

At the bottom is 'AircraftStore' it contains an array of aircraft to be saved. When 'aircraftArray' contains only base objects (Aircraft) the save function works fine. If an inherited object is in the list, NSKeyedArchier.archivedData fails.

import Foundation

class Aircraft: NSObject, NSSecureCoding {
    //MARK: - Properties
    static var supportsSecureCoding: Bool = true
    let registration: String
    var emptyWeight: Double = 1200
    var fuelVolume: Double = 38
    var load: Load = Load()
   
    
    //MARK: - Initialization
    init(withRegistration registration: String = "AC_REG", emptyWeight lbs: Double = 1200) {
        self.registration = registration
        self.emptyWeight = lbs
        super.init()
    }
    
    required init?(coder: NSCoder) {
        registration = coder.decodeObject(forKey: "registration") as! String
        emptyWeight = coder.decodeDouble(forKey: "emptyWeight")
        fuelVolume = coder.decodeDouble(forKey: "fuelVolume")
        load = coder.decodeObject(of: Load.self, forKey: "load") ?? Load()
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(registration, forKey: "registration")
        coder.encode(emptyWeight, forKey: "emptyWeight")
        coder.encode(fuelVolume, forKey: "fuelVolume")
        coder.encode(load, forKey: "load")
    }
}
class C172RS: Aircraft {
    //MARK: - Properties
    var model: Model = .R
    var prop: Prop = .MTV
    var modKit: Bool = false 
    var stc: Bool = false 
    
    //MARK: - Initialization
    override init(withRegistration registration: String = "AC_REG", emptyWeight lbs: Double = 1400) {
        super.init(withRegistration: registration, emptyWeight: lbs)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        model = Model(rawValue: coder.decodeInteger(forKey: "model"))!
        prop = Prop(rawValue: coder.decodeInteger(forKey: "prop"))!
        stc = coder.decodeBool(forKey: "stc")
        modKit = coder.decodeBool(forKey: "modkit")
    }
    
    override func encode(with coder: NSCoder) {
        super.encode(with: coder)
        coder.encode(model.rawValue, forKey: "model")
        coder.encode(prop.rawValue, forKey: "prop")
        coder.encode(stc, forKey: "stc")
        coder.encode(modKit, forKey: "modkit")
    }
}

class AircraftStore {
    //MARK: - Properties
    private var aircraftArray = [Aircraft]()
    private let url = ACFunction.archiveURL(fileName: "AircraftStore.data")
    var count: Int { return aircraftArray.count }
     
    //MARK: - Initialization
    init() {
        do {
            let data = try Data(contentsOf: url)
            if let saveArray = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClasses: [Aircraft.self, NSString.self], from: data) {
                aircraftArray = saveArray as! [Aircraft]
            }
            print("AircraftStore.init unarchive success \(count) aircraft")
        } catch {
            print("AircraftStore.init unarchive failed")
        }
    }
    
    func save() {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: aircraftArray, requiringSecureCoding: true)
            try data.write(to: url)
            print("AircraftStore.save success")
        } catch {
            print("AircraftStore.save failed")
        }
    }

I tweaked your code so that I could actually run it. On doing that, I encounter a specific problem with the way you’re setting supportsSecureCoding on both Aircraft and its subclasses. Once I fixed that, using the same technique I showed in my example, the program works for me.

Code is below.

Share and Enjoy

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


import Foundation

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

    let registration: String
    var emptyWeight: Double = 1200
    var fuelVolume: Double = 38
   
    init(withRegistration registration: String = "AC_REG", emptyWeight lbs: Double = 1200) {
        self.registration = registration
        self.emptyWeight = lbs
        super.init()
    }
    
    required init?(coder: NSCoder) {
        registration = coder.decodeObject(forKey: "registration") as! String
        emptyWeight = coder.decodeDouble(forKey: "emptyWeight")
        fuelVolume = coder.decodeDouble(forKey: "fuelVolume")
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(registration, forKey: "registration")
        coder.encode(emptyWeight, forKey: "emptyWeight")
        coder.encode(fuelVolume, forKey: "fuelVolume")
    }
}

class C172RS: Aircraft {
    override class var supportsSecureCoding: Bool { true }

    var modKit: Bool = false
    var stc: Bool = false
    
    override init(withRegistration registration: String = "AC_REG", emptyWeight lbs: Double = 1400) {
        super.init(withRegistration: registration, emptyWeight: lbs)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        stc = coder.decodeBool(forKey: "stc")
        modKit = coder.decodeBool(forKey: "modkit")
    }
    
    override func encode(with coder: NSCoder) {
        super.encode(with: coder)
        coder.encode(stc, forKey: "stc")
        coder.encode(modKit, forKey: "modkit")
    }
}

func test() throws {
    let input = [
        C172RS(),
        Aircraft(),
        C172RS()
    ]
    let archive = try NSKeyedArchiver.archivedData(
        withRootObject: input,
        requiringSecureCoding: true
    )
    print(archive.count)
}

try test()

Well, I don't completely understand it, but I used it and it works. Looks like I have some reading to do. Thanks for your help.

Well, I don't completely understand it

Yeah, this is a bit of an edge case.

My code is using a class variable (class var supportsSecureCoding) whereas yours is using a static variable (static var supportsSecureCoding). Secure coding was designed around a class variable because of its Objective-C heritage. Specifically, each class that you work with must add its own override of supportsSecureCoding to independently ****** that it supports secure coding.

You’re not the only one confused by this; when you click the Fix button in the Xcode error, it adds a static rather than a class variable (r. 114924432)-:

Share and Enjoy

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

NSkeyedArchiver fails when trying to archive subclass objects
 
 
Q