Use bikeshare to get around? Make sure there are always docks where you’re going, and bikes when you’re leaving, with
Swift 3 brought with it lots of breaking changes, making the process of upgrading from 2.x quite an undertaking.
Most of the changes are handled reasonably well by Xcode’s upgrade tool, but some changes cause issues not found until runtime, such as decoding basic types using
Encoding & Decoding
If you’ve written iOS projects before, you probably know
For those of you who don’t, these are the services used for storing data locally in a hierarchy of objects. (You can read more here.)
In older versions of Swift, you’d decode most things using
let myOptionalString = coder.decodeObjectForKey("key1") as? String let myOptionalInteger = coder.decodeObjectForKey("key2") as? Int
And if you were certain that the values were non-nil during the
encode step, you could unwrap them:
let myString = coder.decodeObjectForKey("key1") as! String let myInteger = coder.decodeObjectForKey("key2") as! Int
One of the more significant changes to Swift 3 syntax is that it moved parameter names out of the method and into the parameter label.
For example, in this case,
decodeObjectForKey("key") is now called using
Thus, the Xcode upgrade tool changed how to decode a required string to:
let myString = coder.decodeObject(forKey: "key1") as! String
And for basic types, eg
Bool, etc, it changed the method altogether to decode the specific type:
let myInteger = coder.decodeInteger(forKey: "key2")
Alright, so what’s the problem? The new syntax looks cleaner, is easier to understand, and according to Apple’s docs, things should default reasonably.
The problem is that the values encoded using Swift 2.x aren’t compatible.
So if you encoded a boolean using, say, Swift 2.3:
//swift 2.3 coder.encode(true, forKey: "myBooleanKey")
and then try to decode it using a build created with Swift 3:
//swift 3 let myBool = coder.decodeBool(forKey: "myBooleanKey")
it would raise a nasty exception:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
(To be clear, this would not cause any issues if you uninstalled the old build before installing the new, making it a problem likely not caught by the developer who is constantly rebuilding, or unit tests that persist only for a specific test case.)
You can solve this by handling both cases: attempt to
decodeObject first, and then fall back to decoding the specific type, eg
let myBool = coder.decodeObject(forKey: "myBooleanKey") as? Bool ?? coder.decodeBool(forKey: "myBooleanKey")
(In this case, if there is no value at key
decodeBool:forKey will return false gracefully.)
You won’t need to do this for non basic types, such as strings or objects.