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?
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.