How to Convert a full ObjectiveC app to Swift
At GoogleCast, we migrated a reference iOS app to Swift using an app, compiler, simple rules and a linter. Here’s what we learned.
Recently @ToddKerpelman and I converted a reference iOS Google Cast app to Swift. Before, I implemented Firebase iOS quickstarts. I created both ObjectiveC and Swift target at the same time. But I never tried to migrate a full app to Swift before. So I welcomed this challenge.
Blame me being lazy. I didn’t want to do to mundane work of declaring each variable and function in Swift. I decided to take a leap in faith and try a converter tool as a starter. I was going to see how good it can translate. Then I was going to catch build errors and bugs after checking line by line. Finally I was going through with a linter tool to catch any bad style.
After googling “ObjectiveC to Swift converter” I decided to give a try to Swiftify. I did a test on a small file, it looked good. Then I uploaded the whole project, pressed convert button, closed my eyes and hoped for the best.
What I received looked like a Swift code. There were double definitions of the variables as they got imported from both .h and .m. Some of the definitions that should’ve been on the top of the file were on the bottom. But in general it looked like I saved tons of hours of mundane work to fix dots and parentheses. Because of the original code, I even saw definitions like
private(set) public var. We started with 269 build errors (more like 800 as you fix, you get more :). Luckily there were lots of low hanging fruits:
- double definitions of vars
- wrong optionality, function signatures
- Swift 3 style issues (enumeration naming)
- Xcode suggested fixes (nullability checks).
This took 3 days as we go down all the way to 2 digits. Now the no-brainer bugs were gone. We had to use assistant view to fix remaining bugs side by side. Most of the bugs turned out to be wrong guesses on the optionality types of class vars. Converter always picks implicitly unwrapped variables instead of optionals. Or it would try to guess local variable types wrong. (You don’t need to declare variable types most of the times as Swift can infer them). Only because I was eager to see the app built, I took a shortcut. I used force unwrapping and force casting only to get rid of build errors. Little did I know, I was going to pay a high price for this later on debugging. After bunch of var to let conversions (converter defined all variables as var), we had an app that built!
Don’t get so hyped yet, we were only beginning. Yes, our app was building, but returning no data and crashing at startup. We spent the next 100 hours debugging to find all the
- null errors
if not nullchecks,
- and lots of optionality fixes.
After those, our app was initializing. It was “almost” casting, yet another force unwrapping error.
There is a reason for style guides. They help you to use the language the way it was designed to be used, and they prevent most bugs before you create them. At that moment SwiftLint came to rescue. You can run SwiftLint on Xcode to see all the style guide exceptions on the lines they occur, and fix them quick. I was determined to get rid of all optionality bugs and stay close to style guide as much as possible. I enabled even the opt-in rules. Fixing style not only helped the readability, but also improved our confidence on the errors. We found further bugs on the way. We got rid of all the force unwrapping, force casting, explicit type declarations, implicitly unwrapped optionals. We favored
if let and
guard let statements wherever possible.
We will be refactoring the app in next weeks, but at least, we have a 1-to-1 swift port of the CastVideos sample app now.
- Swiftify saved us valuable time instead of converting the project line by line. (As long as you now it’s not going to do the all work for you. It’ll be a guesstimate)
- Never ever use
!(unwrapping symbol) unless you have no other choice. Force unwrapping
myVar!, force casting
as!, implicitly unwrapped optionals
Type!are very prone to errors.
- Replacing them with
guard let, and optional chaining
x?.y?.do()will recover your app from exceptions instead of crashing and will help you in debugging.
- Style guides both improves readability and make your code less prone to bugs. SwiftLint was a great tool to force the Swift style guide.