I recently have been working on migrating an app written in Swift 2 to Swift 3. The process of updating a medium-sized app(10 screens, about 12k lines of code, over 100 files) took 3 days. Here is how it went and what I learned along the way.
Use the migration tool they said, it will be fun they said…
The first surprise you get when you open a Swift 2 project with the latest Xcode version is a prompt asking you if you want to convert to Swift 3 syntax. Well, if Apple provides a tool to ease the migration process, we should use it, I guess… As you can imagine, the conversion tool is not perfect. It took care of finding Enums declarations using uppercase case members, wrong parameters label in methods call/declarations or some NSClasses renamed to non-NS equivalent. It was pretty long to inspect every change the tool suggested making sure it was logical. About half a day in the migrate-and-compare tool I was satisfied with most of the changes, so I clicked the save button thinking the job was done… What a foolish thought… After the tool processed the files, I had 177 compiler errors and many warnings. Now began a game of click-the-red-circle-to-display-the-suggested-fix-then-press-enter-to-apply-it to fix all those. It was pretty cumbersome to do and frustrating because most of the errors were usages of the Enums or method previously updated by the tool which were not matching the new syntax. The tool modified the declaration but not the usage in the code. At the end of the day, I was happy to have brought down the errors count significantly.
But how surprised was I the next day when I reopened the project and built it to find that I had 199 errors to fix. Yep, that’s 22 more than the day before… At that moment, I realized that Xcode was messed up by so many errors and it could not display all the errors at once. Well, I did what I had to and fixed most of the trivial errors.
Now begins the real work
As I said, these were trivial errors, the remaining needed more fine-grained inspection. First, as the project is using Cocoapods to manage dependencies, and the pods currently installed were using an outdated Swift syntax. A simple pod update did the job to update the dependency and luckily they were well maintained and updated to Swift 3. That made me think though, if the project had been using obscure pods, the outcome might have been completely different. If the dependencies would not have been updated, I would have needed to search the web for an updated fork or even create it myself. That really brought up a questioning on using third-party dependencies in a project, which I might talk in another article someday.
Apple changed some of their APIs with this Swift release, such as TableViews delegate or CoreData fetch request results. TableViews delegate and data sources have new signature which can be updated pretty easily, but manually because Xcode, well in my case, did not catch it in the migration tool. Also, NSFetchedRequestResults are now generic, that makes our code more readable and type-safe with less casting everywhere we want to use the result of a fetch request.
Finally, the major change I encountered is the object-oriented refactoring of the dispatch APIs. While it makes them more pleasant to use, these modifications required careful analysis before fix. When using the dispatch_after method, we were used to multiplying the desired delay in seconds by NSEC to get a delay in nanoseconds. Now, the API is more flexible, we can use various units when manipulating DispatchTime objects. Also, dispatch_after has been replaced by using a lazy variable initialized by a closure. So by calling that variable, the closure would run only once, the first time it is requested. I also learned about DispatchGroup to handle exclusive concurrent access to a shared resource. You define a critical section of code, and you call a wait method so subsequent calls wait if the critical section has already been entered. What makes it interesting is that you can give a wait delay to the blocking wait call, and that delay can be DispatchTime.now so the call becomes non-blocking. You then only have to validate the return value, .success or . timedOut, to decide the code to execute next.
Change is the only constant
While it might seem very counter productive to work with an ever-changing(even sometimes breaking your builds) technology, we work in a fast-paced environment, we must adapt to keep going and innovate. We will see if I change my mind later in my career, if I will simply want the things I build to work, not fight with the tools…
Any comments? Hit me up @codingjames