Search code examples
iosswiftnskeyedarchiver

Cannot archive custom class object with NSKeyedArchiver


I am trying to perform a simple archive operation to my custom class Car. I followed Apple documentation and made it conform to Codable protocol:

class Car: NSObject, Codable {
    var name: String!

    init(name:String) {
        self.name = name
    }
}

And i want to archive it like:

let car = Car(name: "Ferrari")
let data = NSKeyedArchiver.archivedData(withRootObject: car)

But in the second line the app crashes and i get the following error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_TtCC11Testing6MainVC3Car encodeWithCoder:]: unrecognized selector sent to instance 0x1c4035880' libc++abi.dylib: terminating with uncaught exception of type NSException

I've searched SO but i only found solutions for structs, whereas i am using a class. What can i do?


Solution

  • The NSKeyedArchiver.archivedData(withRootObject:) method should be used on objects that conform to NSCoding NOT Codable/Encodable/Decodable.

    If you want your object to implement the Codable protocol you can use the JSONEncoder and JSONDecoder like below to achieve the same thing:

    let car = Car(name: "Ferrari")
    if let encodedCarData: Data = try? JSONEncoder().encode(car) {
        let decodedCar = try? JSONDecoder().decode(Car.self, from: encodedCarData)
    }
    

    If you wish to use the NSKeyedArchiver instead, you can use the example below:

    class Car: NSObject, NSSecureCoding {
    
        static var supportsSecureCoding: Bool { return true }
    
        var name: String
    
        init(name: String) {
            self.name = name
        }
    
        required init?(coder aDecoder: NSCoder) {
            guard let name = aDecoder.decodeObject(forKey: "name") as? String else { return nil }
            self.name = name
        }
    
        func encode(with aCoder: NSCoder) {
            aCoder.encode(name, forKey: "name")
        }
    
    }
    
    let car = Car(name: "Ferrari")
    if let encodedCarData = 
        try? NSKeyedArchiver.archivedData(withRootObject: car, 
                                          requiringSecureCoding: false) {
        let decodedCar = 
            try NSKeyedUnarchiver.unarchivedObject(ofClass: Car.self, 
                                                   from: encodedCarData)
    }
    

    You should also note, archivedData(withRootObject:) was deprecated in iOS 12.0 and you should use +archivedDataWithRootObject:requiringSecureCoding:error: instead.