Maybe you have read my series of posts about LinkedLists. It was great fun for me to put these things together, but recently, I stumbled over a problem with serialisation, esp. the Codable protocol and references to objects. In Swift 5 there are two Encoder/Decoder pairs available:
- PropertyListEncoder/PropertyListDecoder
- JSONEncoder/JSONDecoder
It turns out that these encoders are not really suitable for archiving object graphs because they expand all references to objects, i.e. put the objects instead of the references in the archive. If you have a simple tree of objects and you do not care about doublets that may be fine but as soon as you have loops in your object graph you are in trouble. Encoding such a graph with these two encoders lead to an endless loop. This may happen quite frequently; just think about delegation where you typically have references of the delegate and its master against each other.
There are lengthy discussions about this fact and it looks as if the Swift developers are just not finished with this subject. There are two workarounds proposed:
- Build your own encoder/decoder pair which respect references to objects correctly.
- Build your own layer of reference substitution during encoding and decoding, i.e. encode unique identifiers instead of the references to objects and encode the objects separately and for decoding decode the objects first and replace the unique identifiers with the references to the objects in a second step.
Both ways are far beyond being trivial (at least for me) and I finally gave up and wait until the Swift developers are ready for this. Fortunately there is an encoder/decoder pair available which respects the references to objects correctly and that’s the old NSKeyedArchiver/NSKeyedUnarchiver of the Objective C-world.
And why do I mention all this in the context of my LinkedLists? Well, in the implementation I described in the previous posts I required the classes, structs or enums in the LinkedList to be Codable since I assumed that has to be the modern way of serialising. So I have to make my items in the LinkedList being Codable even if they contain references to other objects which may end up in a disaster if I try to encode my linked list.
So, first of all I have to make the requirement of Codable of the items in the LinkedList being optional. If the items contain references to other objects (class type) they should not be Codable; but maybe I want to store them in a LinkedList anyway. That’s not too difficult. We just remove the requirements of the generic type T
1 |
where T: Codable |
from the declarations of our classes (i.e. Node, LinkedListIterartor and finally LinkedList). And, of course the protocol conformance Codable from the LinkedList. And then we put our encoding and decoding methods in an extension to LinkedList:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
extension LinkedList: Codable where T: Codable { public convenience init(from decoder: Decoder) throws { var values = try decoder.unkeyedContainer() // get the container from the decoder self.init() if let list = try? values.decode([T].self) { // and decode the array for object in list { // iterate over all elements self.append(value: object) // and append it to our list } } else { throw CodingError.decoding("Decoding Error: \(dump(values))") // had no success } } public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() // get an empty container first var next = head // start at the top var list = [T]() // create an array of the objects while next != nil { // iterate over the whole list list.append(next!.value) // and append our objects next = next!.next // get the next item } try container.encode(list) // and encode the list } } |
The init(from decoder: Decoder)
can’t be flagged as „required“ in an extension; so the class LinkedList has to be flagged as „final“.
So, let’s test if we still can encode Codable types. An array of Strings is surely a Codable object:
1 2 3 4 5 6 7 |
let stringArray = ["1. item", "2. item", "3. item"] let mySecondList = LinkedList<String>(stringArray) Swift.print("The second list: \(mySecondList)") let data = try PropertyListEncoder().encode(mySecondList) let myDecodedList = try PropertyListDecoder().decode(LinkedList<String>.self, from: data) Swift.print("The decoded list: \(myDecodedList)") |
Yes, that still works:
The second list: [1. item, 2. item, 3. item]
The decoded list: [1. item, 2. item, 3. item]
And let’s create a type not being Codable and put it in a LinkedList:
1 2 3 4 5 6 7 8 9 |
struct Person { var name: String var age: Int } let me = Person(name: "Frank-Peter", age: 62) let personArray = [me] let personList = LinkedList<Person>(personArray) print("\(personList)") |
And that works as well as long as we do not try to encode this LinkedList:
[Person(name: "Frank-Peter", age: 62)]
If we do try to encode this list we get a somewhat misleading compiler error:
1 |
let data2 = try PropertyListEncoder().encode(personList) |
Generic parameter ‚Value‘ could not be inferred
So, this was the first step and for the second one I’ll write a separate post. Stay tuned.
Here is the playground of this exercise.