decodeObjectForKey of Swift2 converted in Swift3

I converted code to Swift3 and got bug in decoding object.


Swift2 was :

   required init(coder decoder: NSCoder) {
        if let colorInt = decoder.decodeObjectForKey(colorKey) as? Int {  
            self.color = ColorTag(rawValue: colorInt) ?? .Black // NSColor
        }
    }


    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(self.color.rawValue, forKey: colorKey)
    }
}


This is converted in Swift3 as :

    required init(coder decoder: NSCoder) {
        if let colorInt = decoder.decodeObject(forKey: colorKey) as? Int {    // 25.12.2016
            self.color = ColorTag(rawValue: colorInt) ?? .black // NSColor
        } 
    }

    func encode(with coder: NSCoder) {
        coder.encode(self.color.rawValue, forKey: colorKey)
    }
}


Problem is that if let colorInt = decoder.decodeObject(forKey: colorKey) as? Int always returns nil

If I change decodeObject with decodeInteger, that works.


How is it that decoder.decodeObject cannot be cast as? Int

Are there many other traps like this in conversion ? Testing will be daunting.

I think the issue is the removal of much automatic bridging in Swift 3.


In your Swift 2 code, the object is actually encoded as NSNumber because of automatic bridging.


In your Swift 3 code, I believe "coder.encode(…" is actually using a Swift-specific generic function overlay, so it's actually calling the "encodeInteger" method underneath.


If you want to know what's going on at decoding time, I'd try writing it like this:


let decodedObject = decoder.decodeObjectForKey(colorKey)
let colorInt = decodedObject as? Int


Then use the debugger to step before line 2 is executed, and find out what the class of decodedObject actually is.

Thanks.


But

let decodedObject = decoder.decodeObject(forKey: colorKey)

returns nil.


I probably also have to change the encode ?


But I may loose a nice feature.

In the Swift2 version, if the key did not exist in the file, it was easy to give a default value, then save a key value when rewriting the file : that provided for nice upward compatibility between App versions.

That seems much more difficult now. and a bit messy.


Found a proposal in SO : h ttps://stackoverflow.com/questions/37980432/swift-3-saving-and-retrieving-custom-object-from-userdefaults

If you have previously saved data that was encoded with an older version of Swift, those values must be decoded using decodeObject(), however once you re-encode the data using encode(...) it can no longer be decoded with decodeObject() if it's a value type. Therefore Markus Wyss's answer will allow you to handle the case where the data was encoded using either Swift version:

self.age = aDecoder.decodeObject(forKey: "age") as? Int ?? aDecoder.decodeInteger(forKey: "age")


But I do need anyway to change encoding as well.

Accepted Answer

You didn't say whether it's failing on archives you created with the old or new versions of your code, but the answer you got on SO sounds very reasonable — basically, try both kinds of decode.


If you don't care about opening old archives, then I'd suggest changing your Swift 3 code to be internally consistent and hack-free.


Note that the situation may change again in Swift 4 (with Codable conformance), but it isn't fully implemented yet. The changes won't affect your existing code, except that many basic types (like Int) will conform to Codable automatically, and it's not clear yet what the pitfalls are going to be for types that fit into both the Codable and NSCoding universes.

Thanks for your help.


Yes, I opened file created by Swift2.


So, I've implemented the following:


        if decoder.containsValue(forKey: colorKey) { /
            let colorInt = decoder.decodeObject(forKey: colorKey) as? Int ?? decoder.decodeInteger(forKey: colorKey)
            self.color = ColorTag(rawValue: colorInt) ?? .black 
        } else {             
            self.color = .black
        }


I wait for Swift4, becauise universal encoder, even for own classes will be a big plus.

decodeObjectForKey of Swift2 converted in Swift3
 
 
Q