In testing how the new Codable interacts with NSCoding I have put together a playground test involving an NSCoding using Class that contains a Codable structure. To whit
struct Unward: Codable {
var id: Int
var job: String
}
class Akward: NSObject, NSCoding {
var name: String
var more: Unward
init(name: String, more: Unward) {
self.name = name
self.more = more
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(more, forKey: "more")
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
more = aDecoder.decodeObject(forKey: "more") as? Unward ?? Unward(id: -1, job: "unk")
super.init()
}
}
var upone = Unward(id: 12, job: "testing")
var adone = Akward(name: "Adrian", more: upone)
The above is accepted by the Playground and does not generate any complier errors.
If, however, I try out Saving adone, as so:
let encodeit = NSKeyedArchiver.archivedData(withRootObject: adone)
The playground promptly crashes with the error:
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
Why? Is there any way to have an NSCoding class contain a Codable structure?
The actual error you are getting is:
-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance
And this is coming from the line:
aCoder.encode(more, forKey: "more")
The cause of the problem is that more
(of type Unward
) doesn't conform to NSCoding
. But a Swift struct
can't conform to NSCoding
. You need to change Unward
to be a class that extends NSObject
in addition to conforming to NSCoding
. None of this affects the ability to conform to Codable
.
Here's your updated classes:
class Unward: NSObject, Codable, NSCoding {
var id: Int
var job: String
init(id: Int, job: String) {
self.id = id
self.job = job
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(job, forKey: "job")
}
required init?(coder aDecoder: NSCoder) {
id = aDecoder.decodeInteger(forKey: "id")
job = aDecoder.decodeObject(forKey: "job") as? String ?? ""
}
}
class Akward: NSObject, Codable, NSCoding {
var name: String
var more: Unward
init(name: String, more: Unward) {
self.name = name
self.more = more
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(more, forKey: "more")
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
more = aDecoder.decodeObject(forKey: "more") as? Unward ?? Unward(id: -1, job: "unk")
}
}
And your test values:
var upone = Unward(id: 12, job: "testing")
var adone = Akward(name: "Adrian", more: upone)
You can now archive and unarchive:
let encodeit = NSKeyedArchiver.archivedData(withRootObject: adone)
let redone = NSKeyedUnarchiver.unarchiveObject(with: encodeit) as! Akward
And you can encode and decode:
let enc = try! JSONEncoder().encode(adone)
let dec = try! JSONDecoder().decode(Akward.self, from: enc)