Swift 2 to Swift 5

For fun, I recently migrated an old toy project from Swift2 to Swift5.  

The project is an iOS app that prompts a user to press keys on a bluetooth keyboard.     I originally threw it together for one of my kids that was obsessed with my wireless bluetooth keyboard and was at exactly the right age to start working on letter identification with their abc's.

In the process of migrating the source and project config to the latest Swift, I spotted a couple of interesting changes to the language.

Swift

Increment operator considered confusing?

As of Swift3, they got rid of prefix/postfix decrement/increment.

index++

now needs to be

index+=1

The context of that decision is recorded in SE-0004.   While it was not causing me any noticeable pain, I can appreciate the reasoning they laid out.  Further, I really appreciate being able to go back and look up that reasoning in the Swift Evolution documents.  

Selector references

When you write Swift code that needs to handle a callback from an Objective C class, you need a way to expose that Swift method.

It used to be that referencing a selector would require constructing a unchecked  string to precisely identify a method.  If that string was not 100% correct, it would fail as a runtime error.

As part of Swift 2.2, tying Swift code to callback selectors for Obj-C classes changed to become more strongly typed. (YES!)

For example:

externalKeyboardKeys(Selector("sayKey:"))

becomes

externalKeyboardKeys(callback: #selector(sayKey))

The context of that decision, implemented in Swift 2.2, is recorded in SE-0022 .  

Random

How many times, in how many languages, have I made something that calculates a random index and then pulls an element out of a Collection, from that index?  Many times.   Mostly in games.  I had not really given it much thought.

It typically looks like this:

let letters = "abcdefghijklmnopqrstuvwxyz"
let randomIndex = Int.random(in: 0 ..< letters.count)
let randomLetter = letters[randomIndex]

Note the half open range ( ..< ) which expresses that the end of the range is exclusive of letters.count.

Was that a difficult few lines of code to write?  

No...   but look at this:

let letters = Array("abcdefghijklmnopqrstuvwxyz")
let randomLetter = letters.randomElement()!

It's more expressive and concise.  

Note the trailing force unwrap (!) above is ok because letters is a const that always has elements.   randomElement() returns an Optional which could be nil if the Array or Collection were empty.  In this case, it will never be empty, so it is ok.

The context of the decision to add some collection helper methods and other improvements around random functionality which were implemented in Swift 4.2, are recorded in SE-0202 .

ObjectiveC<->Swift mappings

Most of the other changes, in the context of this project have to do with how method and constant names are mapped between legacy Apple ObjectiveC API's and Swift.   They are all, at a minimum, readability improvements, and in some cases syntactic sugar to enable better interoperability between Cocoa touch classes built in ObjectiveC and Swift.  

I'm a fan but I don't have a lot to say about them.  If you're curious, there's a PR here which shows the full scope of changes, broken down in commits to go from Swift2 to Swift5.   I might have squeezed in a commit or two to improve the kinds of things you only notice when taking a second glance at code you have not looked at in years.  �:win

Retrospective

Overall, this was a great little project to learn some of the edges of migrating Swift 2 to Swift 5.   I'm sure there are plenty more edges that might only be exposed on a larger project.  Something that could be especially painful are projects with external 3rd party library dependencies, which could be at different Swift revision levels.

When trying to leverage Xcode's built in 'Convert to Current Swift Syntax' feature, there was some general tooling pain, because you have to roll back to old versions of MacOS and Xcode and do a few hops to get from Swift2->Swift3->Swift4->Swift5.  It's pretty cool that Xcode even includes a feature to do this at all.  I can't remember where I last saw something like that that.  However, it was surprising to me that each version of Xcode was bound in what source and destination version of Swift it could translate from and to.  I'm guessing there is an implementation detail that makes it hard.  🤷‍♂️  As swift source and binary formats stabilize (Swift 4,5,6), perhaps this becomes a non-issue and that feature's importance drops off significantly.

Since this was a smaller project, I chose not to go back to an old MacOS and went ahead and pushed some things forward manually.  For a larger project, I would probably find a way to spin up an old version of MacOS (Sierra + Xcode 8.3.3) to grease the skids for that Swift2->Swift3 part of the conversion.

It would have been easier if I just regularly kept things up to date, updating for each major Swift release as it happened.   Waiting so long to update a project meant doing the juggle of Xcode versions.  

Note the scars of time, for historical purposes:

Now that I discovered versions 8.3.3 and older can't launch on Mojave, say hello to my little friend:

🗑

Woot.