0

I'm experimenting with NSKeyedArchiver because I'm looking into saving an object graph. However, there seems to be a problem with objects referencing the same instance of another object. Say, I have three classes: A, B and C that reference each other like this:

A --> B <-- C

A and C each have a reference to the same object B. When decoding this using NSKeyedUnarchiver, it simply creates multiple instances of B so that A and B do not reference the same object anymore.

Here's a full example:

import Foundation

class A: Codable {
    var b: B
    
    init(b: B) {
        self.b = b
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.b = try container.decode(B.self, forKey: .b)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(b, forKey: .b)
    }
    
    private enum CodingKeys: String, CodingKey {
        case b
    }
}

class B: Codable {
    init() {}
    required init(from decoder: Decoder) throws {}
    func encode(to encoder: Encoder) throws {}
}

class C: Codable {
    var b: B
    
    init(b: B) {
        self.b = b
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.b = try container.decode(B.self, forKey: .b)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(b, forKey: .b)
    }
    
    private enum CodingKeys: String, CodingKey {
        case b
    }
}

class Store: Codable {
    var a: A
    var b: B
    var c: C
    
    init() {
        b = B()
        a = A(b: b)
        c = C(b: b)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.a = try container.decode(A.self, forKey: .a)
        self.b = try container.decode(B.self, forKey: .b)
        self.c = try container.decode(C.self, forKey: .c)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(a, forKey: .a)
        try container.encode(b, forKey: .b)
        try container.encode(c, forKey: .c)
    }
    
    private enum CodingKeys: String, CodingKey {
        case a, b, c
    }
}

let store = Store()

let archiver = NSKeyedArchiver(requiringSecureCoding: false)
try! archiver.encodeEncodable(store, forKey: NSKeyedArchiveRootObjectKey)
archiver.finishEncoding()

let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: archiver.encodedData)
if let decodedStore = try! unarchiver.decodeTopLevelDecodable(Store.self, forKey: NSKeyedArchiveRootObjectKey) {
    // Will print false
    print(decodedStore.a.b === decodedStore.c.b)
}

Am I doing something wrong or does something like this simply not work? Or is my example flawed?

Thanks!

SwiftedMind
  • 3,701
  • 3
  • 29
  • 63
  • Hm okay, maybe I need to take another route to store something like this. My idea for this came from an answer to a similar question: https://stackoverflow.com/a/48480708 I might be wrong about this, but it sounds like `NSKeyedArchiver` manages these kinds of relationships by assigning ids to the objects while encoding so that it can correctly reference them while decoding (and thus prevent duplicates). But maybe my understanding of that is wrong – SwiftedMind Jul 04 '21 at 20:43
  • Just saw your edit. You're right, it's obviously an init so it creates another instance. Totally overlooked that. Hm, that's unfortunate. – SwiftedMind Jul 04 '21 at 20:46
  • Thanks, I will look into that. But I thought `NSKeyedArchiver` is using `NSCoding`? – SwiftedMind Jul 04 '21 at 21:05
  • Oh okay, that's a shame. So I guess `encodeEncodable` is using something else then? Damn, then I guess I have to do it via `NSCoding` – SwiftedMind Jul 04 '21 at 21:17

0 Answers0