Swift 3 conversion and refactoring your project

A few weeks ago Apple announced the deprecation of Swift 2.3 starting with the release of Xcode 8.2. Reason enough to start the Swift 3 conversion of the Videoland iOS app.

As the project contains many files, this couldn’t be less than challenging. The project was setup in the end of 2014, starting in Swift 1.1. Reason for me to also start a big refactor and code improvement.

This refactor gave me some insights and results which can help you convert your project way faster.

Steps of Swift 3 conversion:

#1 – Read blogposts

Read blogpost to help you out and give some tips before starting the conversion. I especially found this one useful:
Yammer iOS App ported to Swift 3

#2 – Check dependencies

Make sure all dependencies are available in Swift 3 and if not, you’re able to replace the framework or fix the conversion yourself.
I also had to convert some personal frameworks and internal frameworks before I could start the main project conversion

#3 – Deprecated dependencies

Some dependencies were deprecated because of Swift 3. In my case this was ReactiveCocoa in favour of ReactiveSwift. This resulted in many RACSignals needing to be replaced.
It’s easier to replace these frameworks with a fully tested project which builds successfully. After the Swift 3 conversion you’ve got a lot to test already, so you don’t want to test a new dependency as well.

#4 – Start the conversion to Swift 3 with the Xcode converter, multiple times

The Xcode converter helps you to convert your code faster. After the conversion you still have a lot to fix, but it’s a good start.
After fixing some errors, you can run the converter again. To do this, make sure your project settings is still as followed:

Legacy Swift Project Setting in Xcode

Legacy Swift Project Setting in Xcode

#5 – Use find & replace

Use it a lot! I used PureLayout as a dependency and had to replace enums like .Top with .top. Find replace was really fast to fix these kind of issues.

#6 – Skip complex issues

Fixing complex issues is hard when your compiler is busy with all the Swifty errors and doesn’t help you with autocompletion. It’s way easier to just fix these later when the project builds successfully.
Simply add a // TODO: marker and fix complex code conversions later.

#7 – Continue until it builds

Just repeat these steps until your project builds again!

#8 – Refactor after successful compilation

After your project is building successfully again, it’s still far away from stable. It will probably have some new crashes and feels very unstable. Therefor it’s good to start with some good old run & test to see what happens and fix bugs & crashes.

#9 – Fix your todo’s

If you’ve marked complex issues, this is the moment to fix these.

#10 – Access Control Fixes

Access Control with Swift influences compiler build time. More info can be found by reading the Apple blogpost: Apple – Access Control
I used find & replace a lot:

  • Replace all fileprivate with private
    • The Xcode converter replaces private with fileprivate by default. For Videoland it unnecessary converted fileprivate to private in 266 files!
  • Replace class with final class
    • Final class can’t be inherited. Afterwards, fix the errors and see which classes really can’t be final
  • Replace class func with static func
    • Just like final, you can’t override static functions. This will help the compiler as it doesn’t have to look for any overwrites
  • Replace class var with static var (or even static let if possible)
  • I came across some old fashioned singletons which could be defined way easier

#11 – Search for // FIXME:

The Swift 3 conversion with the Xcode converter adds // FIXME: equatable operators to make it possible to compare with optionals. In Swift 3 you should unwrap your variables before comparison.
Remove the fileprivate operators and fix the errors

#12 – Fix deprecations

I had some deprecated functions and methods (MPMovieFinishReason, MPMoviePlaybackState). When you’re refactoring and you already have to test your app again, it’s a perfect time to also fix these deprecations.

#13 – Replace NSArray, NSMutableArray, NSDictionary

As Videoland was my first Swift project, I still used some Objective-C code styles. I converted these to the Swift variants.

#14 – Find & Replace the default enum values

I replaced values like UIControlState() with UIControlState.normal. This is the list I know so far:

  • UIControlState() with .normal
  • UIViewAnimationOptions() with .curveEaseInOut

#15 – Update frameworks

I was still using SDWebImage and I wanted to update my frameworks to be as much Swift as possible. After diving into the new Swift frameworks, I came across Nuke, which shows these benchmarks between all popular frameworks:
Benchmarks
Benchmark wmo

Unfortunately I still had to support iOS 8, so I couldn’t use Nuke. However, Kingfisher supports iOS 8 and is heavily inspired by SDWebImage. Migrating my code to Kingfisher was really easy and makes my project a bit more Swifty.

JSON Benchmarks

I also came across JSON parsing library benchmarks at Github. However, the library JASON we’re using isn’t listed. After forking and integrating JASON, the JASON library seems to be faster than Marshal. It could be a lot of work to switch to a new JSON parsing library, but it could bring better performance across your whole app.

#16 – Check your encodings

In Swift 3 methods like encodeInt, encodeBool, all changed to encode(_, forKey:). This could silently break your encoding! If you are using decodeObject() with number values, your encoding could be broken. To be backwards compatible you could easily fix this by adding a cast to Any:

encode(value as Any, forKey:"MyKey")

That’s it!

Hopefully your Swift 3 conversion will be a bit more faster by reading this blogpost. Feel free to tweet me some tips or feedback!

 

Antoine van der Lee

Dutch iOS developer at Triple. Developed apps like Buienradar, Videoland and Pop the Dots.

 
Follow on Feedly