I don't know how to handle this situation properly.
I have coded a value in a class.
When I decode, it should be a Bool, but it may be an Object (if created by an older version of the application, in Swift2 times).
So I used to do this :
var test : Bool
if decoder.containsValue(forKey: detailOptionsKey) { // key exists ; but was it created as Bool or as object (in Swift2)
test = decoder.decodeObject(forKey: detailOptionsKey) as? Bool ?? decoder.decodeBool(forKey: detailOptionsKey)
} else {
test = false
}
But In some cases I get the following crash message :
[General] *** -[NSKeyedUnarchiver decodeBoolForKey:]: value for key (detailOptionsKey) is not a boolean
My problem is that in :
test = decoder.decodeObject(forKey: detailOptionsKey) as? Bool ?? decoder.decodeBool(forKey: detailOptionsKey)
I have no way to test if decodeBool can be called ; but I need to call if value was encoded as a Bool (not an object)
I do not see how to test to avoid a crash.
I wonder if I could use decodeDecodable which can fail (that's what I need)
func decodeDecodable<T>(_ type: T.Type, forKey key: String) -> T? where T : Decodable
I tried
let test = (decoder as! NSKeyedUnarchiver).decodeDecodable(Bool.self, forKey: detailOptionsKey) ?? false
self.detailOptions = decoder.decodeObject(forKey: detailOptionsKey) as? Bool ?? test
which seems to work, but a bit convoluted.
Why do I need Bool.self ? and not just Bool
In anycases, as decodeBool (or decodeInteger, …) may crash, I would need to make my code more robust.
How can we know the type of the key, to use the appropriate decoder ?
>> decodeDecodable
No, you can't do that. "Decodable" (part of "Codable") is the new Swift-only protocol that implements a different mechanism to "NSCoding". You can embed Codable things in a NSCoding archive, but they're not interchangeable with things encoded the old way.
>>Really surprising that the new decode (ie decodeBool) cannot fail but just crash. That seems a very strange design decision not to return an optional or throw an exception. Cannot understand why, when decodeObject returns optional.
You're dealing with a (very old) Obj-C API here. "decodeObject" can return nil because "encodeObject" can put nil in an archive. The nil result is not an error return.
Traditionally, NSCoding's decoding has no error returns. That's because the decoding is done in an "init" method that has no way of indicating an error. (It can return nil, but — as in the "decodeObject" case — nil can sometimes be a valid value.) However, a few years ago, an error return mechanism was added to NSKeyedUnarchiver, but it's confusing because the error is hidden until decoding returns to the top level. To use this mechanism, you need to start decoding using one of the "decodeTopLevel…" methods. As Quinn said, to catch the "bool" decoding failure, you will also need to set the decoding policy to not throw exceptions.
Having said all this, the real problem is that you painted yourself into a corner. You should never have re-used the same key for a different kind of value. The usual way of handling this is to use a different key for the new value type, and to try decoding both keys to see what's there. It's also usual to encode some kind of archive version into the archive itself, which can serve as a discriminator when you accidentally create a problem like this.
You might be able to solve your problem by trying two different decodes at the top level, starting from "decodeTopLevel…", and setting a global variable (or something) to indicate which decode to apply to the bool-or-not key, and setting the appropriate decode policy. If it's not a bool, the entire decode will fail when you get back to the top level, and then you can try the other one.
If that turns out to be impossible, your best strategy might be to write a compatibility method in Obj-C, which wraps the decoding in an @try/@catch construct that should catch the Obj-C exception, and call that method from Swift.