1 Reply
      Latest reply on Mar 19, 2018 1:47 PM by QuinceyMorris
      Ruben Christian Level 1 Level 1 (0 points)

        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_7.MyGraphNode'
        

        Can anyone explain why the test text is ignored during the deserialization?

        Or why I have to add the convenience initializer at all?

        • Re: Deserialize subclass of GKGraphNode using NSKeyedUnarchiver
          QuinceyMorris Level 8 Level 8 (6,020 points)

          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, and it needs to be declared something like this:

           

              private(set) var text: String!
          

           

          I'm not sure exactly why it crashes without an explicit "init()" method, but I guess Swift takes measures to ensure that classes don't silently inherit the default "init()" from NSObject, which would break Swift's own initialization rules.

           

          I don't think there's much you can do about this situation, since it's valid Obj-C that just doesn't translate well into Swift.