Afterthoughts: Swift 3 Conversion

Max Kramer
4 min readDec 29, 2016

--

TLDR; Pain in the ass.

Article follows on from https://blog.maxkramer.co/pre-thoughts-converting-to-swift-3-0-c479c083bde6#.tyiz09u83

Sorry for the delay; it has been a hectic few weeks on both a personal and professional level.

In the last post, I discussed my thoughts on the best way to go ahead in converting our project to Swift 3. I was aware that it wasn’t going to be a quick and easy problem to solve, but I was also aware that it was a necessary evil because of the impending Xcode releases that would no longer support ‘legacy swift’ code (Swift 2.X).

999+ build errors after performing the automatic migration
And if 999+ build warnings weren’t bad enough

As you can see from my tweets above, the entire process was an absolute pain:

  • The migrator migrated around 75% of the code-base correctly, but failed to update enum cases all over the place, which meant they had to be done manually
  • I didn’t account for breaking changes in the DSL of our dependencies
  • We did not do a code-freeze while performing the migration so any work that was done during the migration and before the branch was merged into develop needed to be migrated itself
  • Unit tests also needed migrating to Swift 3

As outlined in the pre-thoughts, I had defined an approach to migrating the project. There were 6 steps involved that revolved around performing the Swift 3 migrator that comes embedded in Xcode 8 as well as updating any external dependencies to prevent any ABI incompatibility.

FYI: The steps that did not cause any issues have been ignored from the following list.

Step 1: Updating third-party dependencies

This step was straightforward. Each of our dependencies had fortunately already begun their own migrations, so we merely needed to update the tag that we were pointing to in the Podfile to use the latest code.

There were some instances, such as with Realm, which doesn’t officially support Swift 3. Fortunately however in all of these cases, there was an unofficial/nightly branch or tag that could be used instead. These branches weren’t completely stable and came with experimental warnings, but given that the next deployment of the app wasn’t coming any time soon and the integration tests were not failing, I felt confident that official releases would come before we needed to do so ourselves.

The biggest issue, however, came when updating ReactiveCocoa to the 5.0.0 alpha build. What I hadn’t taken into account when assessing the difficulty of the migration, was that I didn’t only have to deal with the breaking changes that came from upgrading to Swift 3, but with third-party syntax that had changed to better match the Swift 3 api design guidelines.

Instead of extending `UITextField` to add a couple of convenience methods for binding to changes to its `text` property, a new property reactive had been introduced in its place. ReactiveCocoa is used widely throughout our code-base so it required a significant amount of time to update references to the old behaviour, to the new one.

Step 2: Running the Swift 3 Migrator

For the most part, I have little to complain about when it comes to the migration tool. Most enum cases were correctly modified such that they were camel-cased, and the majority of system level (UIKit/Foundation) method calls were correctly renamed (or parameters renamed) to fit the new syntax.

If you read the last paragraph careful, you’d notice the use of ‘most’ and ‘majority’. A significant proportion of the 999+ build errors were due to the migrator not performing its function on the entirety of our project. Various enums were not correctly renamed nor system level methods. Fortunately they were very easy to fix as Xcode provides a suggestion to fix these types of build warnings, but they were an additional stress that could easily have been avoided.

Step 4: Validating the migration by executing our unit test suite

Guess what? Our unit tests were also written in Swift 2.3 and thus would not compile. Again, a misjudgement on my part that required the migrator to be run again on our unit test target across hundreds of tests.

This meant that the unit tests could not provide a single source of truth as to whether or not the migration was performed successfully because they could easily return false positives from bugs introduced while the migration took place.

The majority of tests passed in the end, other than a few concerning ReactiveSwift and methods that had an incorrect file-level availability so could not be ‘seen’ by the test target.

It took around four full days in the end to perform the entire migration. The great thing about Xcode is that once you have got it down to around 30 errors, which you then fix and rebuild the project, you’re magically donated another 30 that didn’t exist a few minutes ago.

Once you had fixed some issues, you were then magically presented with a handful more.

If I could give any advice towards developers that plan on performing the migration anytime soon, it would be:

  • Don’t migrate until you are fully aware of the extent to which your third-party dependencies may change
  • Perform the Swift 3 migration tool more than once to ensure that all enums and method calls have been renamed correctly
  • Don’t trust the number of compilation errors that Xcode shows in the project navigator. There are more than it says!

If you have any questions or would like some advice on the process, feel free to reach out to me on Twitter! I completely feel your pain…

--

--