I want to serialize and deserialize an object of my GKGraphNode subclass using NSKeyedArchiver and NSKeyedUnarchiver. So I try the following:
//: Playground - noun: a place where people can play
import GameplayKit
class MyGraphNode: GKGraphNode {
static let textCodingKey = "TextCodingKey"
let text: String
override convenience init() {
self.init(text: "Default Text")
}
init(text: String) {
self.text = text
super.init()
}
required init?(coder aDecoder: NSCoder) {
text = aDecoder.decodeObject(forKey: MyGraphNode.textCodingKey) as! String
super.init(coder: aDecoder)
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(text, forKey: MyGraphNode.textCodingKey)
}
}
let text = "Test Text"
let graphNode = MyGraphNode(text: text)
let data = NSKeyedArchiver.archivedData(withRootObject: graphNode)
if let unarchivedGraphNode = NSKeyedUnarchiver.unarchiveObject(with: data) as? MyGraphNode {
print("Text: \(unarchivedGraphNode.text)")
}
Unfortunately the example prints only the default text and not the expected test text:
Text: Default Text
First I omitted the convenience initializer. But in this case it crashed with this error:
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
GKGraphNodeSubclass.playground: 5: 7: Fatal error: Use of unimplemented initializer 'init()' for class '__lldb_expr_58.MyGraphNode'
Can anyone explain why the test text is ignored during the deserialization?
Or why I have to add the convenience initializer at all?
I got help in the Apple Developer Forum:
The "text" property is ending up being reset by "super.init(coder: aDecoder)", presumably because that calls "init()" internally, and that ends up at your convenience "init ()". This would be illegal in Swift, but it's legal in Obj-C, which doesn't have the same strict initialization rules.
The solution is to initialize "text" after the super.init(coder:), rather than before. This means you can't use a "let" property
To fix my example I changed the variable declaration and the NSCoding initializer like this:
var text: String!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
text = aDecoder.decodeObject(forKey: MyGraphNode.textCodingKey) as! String
}