In the process of implementing init(coder:) for a custom NSView subclass, I came across some strange behavior with NSKeyedArchiver and NSKeyedUnarchiver that I still don't entirely understand. Consider this sample code:
let label = NSTextField(labelWithString: "Test")
// Encode
let data = try NSKeyedArchiver.archivedData(withRootObject: label, requiringSecureCoding: false)
// Decode
try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSTextField
This appears to encode and decode an NSTextField as expected. However, if I try to use decodeTopLevelObject() instead of unarchiveTopLevelObjectWithData(_:), the result is nil:
// Encode
let data = try NSKeyedArchiver.archivedData(withRootObject: label, requiringSecureCoding: false)
// Decode
let decoder = try NSKeyedUnarchiver(forReadingFrom: data)
decoder.decodeTopLevelObject() as? NSTextField // nil
Similarly, if I try to use encodedData instead of archivedData(withRootObject:requiringSecureCoding:), the result is nil:
// Encode
let coder = NSKeyedArchiver(requiringSecureCoding: false)
coder.encodeRootObject(label)
let data = coder.encodedData
// Decode
try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSTextField // nil
The result is even nil if I use encode(_:forKey:) and decodeObject(forKey:):
// Encode
let coder = NSKeyedArchiver(requiringSecureCoding: false)
coder.encode(label, forKey: "label")
let data = coder.encodedData
// Decode
let decoder = try NSKeyedUnarchiver(forReadingFrom: data)
decoder.decodeObject(forKey: "label") as? NSTextField // nil
I'm surprised that the first example above appears to work correctly but none of the others do (especially the last one). Could someone help me understand what's going on here?
If you read the documentation for init(forReadingFrom:) it states:
This has probably been the main source of your confusion. Setting requiresSecureCoding back to false, then, will make the following work:
When it comes to encoding and decoding correctly, just make sure you have matching keys. encodeRootObject(_:), which is implemented the same as encode(_:), internally uses the key of nil, so then just call decodeTopLevelObject(), or decodeObject().
On the other hand, NSKeyedArchiver.archivedData(withRootObject:requiringSecureCoding:) uses the key NSKeyedArchiveRootObjectKey, so you could technically decode by performing:
...but you wouldn't want to do this, since it's an internal implementation that theoretically could change. Instead you'd just use NSKeyedArchiver.unarchiveTopLevelObjectWithData(_:), as you did in your working example.