1

I am trying to serialize a custom class containing a reference loop and got it working using NSCoding:

import Foundation

class Person: NSObject, NSCoding {
    let name: String
    weak var parent: Person?
    var child: Person?
    init(name: String) {
        self.name = name
    }
    required init(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
        self.parent = aDecoder.decodeObject(forKey: "parent") as? Person
        self.child =  aDecoder.decodeObject(forKey: "child") as? Person
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
        aCoder.encode(parent, forKey: "parent")
        aCoder.encode(child, forKey: "child")
    }
}

let per = Person(name: "Per")
let linda = Person(name: "Linda")

linda.child = per
per.parent = linda

var people = [Person]()
people.append(linda)
people.append(per)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)

let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: encodedData) as! [Person]
myPeopleList.forEach({
    print("\($0.name)\n\t Child: \($0.child?.name ?? "nil")\n\t Parent: \($0.parent?.name ?? "nil")"

    )}) 
// Linda
//     Child: Per
//     Parent: nil
// Per
//     Child: nil
//     Parent: Linda

No I want to do the same using Codable:

import Foundation

class Person: Codable {
    let name: String
    weak var parent: Person?
    var child: Person?
    init(name: String) {
        self.name = name
    }
}

let per = Person(name: "Per")
let linda = Person(name: "Linda")
linda.child = per
per.parent = linda

var people = [Person]()
people.append(linda)
people.append(per)

let archiver = NSKeyedArchiver()
try archiver.encodeEncodable(people, forKey: NSKeyedArchiveRootObjectKey)

But I get the error:

error: Execution was interrupted, reason: EXC_BAD_ACCESS

During the last line. I assume it has to do with the reference loop, because it works if I comment out the line:

per.parent = linda

So can we use Codable to serialize reference loops? If so, how?

A. Vage
  • 35
  • 5

1 Answers1

1

You can choose which properties are serialized by overriding Coding Keys (from here)

e.g. in your case within the class:

enum CodingKeys: String, CodingKey
{
    case name
    case child
}

Only the keys included here should be saved, so no child->parent loop. This does however mean the connection will only exist in one direction when loading, so you will have to re-connect them when loaded.

FWIW, if you're dealing with 2-way relationships you will be much better off using a database rather than using NSKeyedArchiver for persistence.

Community
  • 1
  • 1
GetSwifty
  • 7,568
  • 1
  • 29
  • 46
  • Thank you for the reply! I guess I will use the NSCoding for now. (Maybe Codable will support reference loops in the future?) I am only using NSKeyedArchiver to create a Data object to give to UIDocument. – A. Vage Mar 01 '18 at 23:45
  • But, yeah maybe UIManagedDocument and Core Data would be better for my use case. Thanks! – A. Vage Mar 02 '18 at 00:00
  • Generally if you're making your data generically encodable/decodable, the relationships need to only go one direction at a time. Any other relationships are usually handled by keys, not hierarchy. GL finding the solution that's right for you! – GetSwifty Mar 02 '18 at 00:03