Transitioning From Objective C to Swift in 4 Steps
All this without rewriting the existing code
By Gergely Orosz
Note: as a follow-up post, for more details on how we made this transition, please see: How We Migrated Our Objective C Projects to Swift — Step By Step
We started developing Skyscanner TravelPro in Objective C in March 2015. A couple of months later, when Swift 2.0 was released, we started to slowly introduce Swift. Fast forward to 8 months later — and 100% of the new code we write is in Swift. All without having rewritten any of our existing, working, robust and tested Objective C code — there would have been little point in doing so.
There are many resources talking about how to decide whether to use Swift for a new project or not, and best practices for writing Swift. However if you’re working on a pretty large Objective C codebase, you will probably find this article useful. If not — one day you might bump into a codebase where you want to start using Swift: this article presents some advice on how to get started doing so.
Here’s a visual representation of how our codebase has changed in 10 months. Since November 2015 all new code is written in Swift, which now makes up about 10% of our 65,000 line codebase — and growing.
So what was our approach when going from Objective C to Swift?
1. Start with a Simple Component
We decided to start as simple as we could: with some isolated classes that could be tested and used by themselves. The first few components we chose were simple UI controls, utility functions and extension methods on existing classes.
For example among the first Swift additions we added in, we wrote a String extension method that made localizing strings much more pleasant to read:
Interesting enough we could have implemented the same functionality using Objective C categories. However it never occurred to the team to use anything other then the good old
NSLocalizedString(@"MyText", @""). With a new language, lots of new ideas surface. So from day one all our Swift strings are written in the pleasant
2. Using Existing Objective C Code From Swift
After writing a couple of standalone Swift components — and unit tests against them — we moved on to using our existing Objective C classes from Swift. Things started to get real.
To use any Objective C classes from Swift you need to define a Swift bridging header. This is a
.h file where you define all your Objective C headers to “expose” for Swift to use. On top of the header itself, the build settings need to be changed for the compiler to pick this up. Once this is done, these Objective C classes are imported into the Swift world, and can be used easily.
When using Objective C classes from Swift, you will likely notice warnings saying
pointer is missing a nullability type specifier. When Objective C code imported into Swift then the compiler checks for nullability compatibility – and if it doesn’t find any information on nullability, then issues this warning. It does this check because in Swift nullability information is always explicitly declared, either with non nullable types or by using optionals.
The only changes we needed to make to our Objective C code was adding nullability information to the header to resolve the warnings issued by the compiler. To do so, we used the new
nonnull annotations. The refactor itself only took a couple of hours, because we only had to change the classes we used in Swift, no more than a couple hundred lines. However, making this change made us think long and hard on what could, or could not be
nil in our existing codebase. In Objective C it was easy enough to brush this under the table, but we couldn’t avoid an explicit choice when exposing this code to Swift.
For the most part this refactor involved changing lines of code like this:
In case of method signatures with blocks, the changes were a bit more complex, but nothing unmanageable:
3. Using Swift Code From Objective C
After having a couple of moderately complex Swift components using our Objective C classes, it was time to use these components from within Objective C. Using Swift components from Objective C code is much more straightforward, as there is no bridging header needed.
The only changes we had to make to our existing Swift files was inheriting from
NSObject or adding the
@objc attribute to classes we wanted to expose. There are some Swift specific classes that cannot be used from Objective C, like structures, tuples and generics and a few others. These limitations didn’t affect us because we didn’t want to expose any of the new structures to Objective C. The only exception where we had to do a little extra work was enums. To use
enums from Swift, in Swift they need to be specified with the
Int value type:
4. (Re-)learn Unit Testing and Dependency Injection with Swift
Once we had some more complex components with dependencies, we hit an issue for which there was no obvious solution. This issue was unit testing. Unlike Objective C, Swift does not support readwrite reflection. Put it simply: there is no OCMock equivalent in Swift, in fact mocking frameworks don’t exist.
Here’s an example that caused us to scratch our heads. We wanted to test that when pressing the submit button on a page, the
saveTrip method is invoked on the
viewModel property of the
view object. In Objective C, using
OCMock, this could be tested in a similar way:
In Swift this approach would not work. In Objective C unit testing is usually done with the help of rich mocking frameworks like OCMock. Dependency injection is a good practice, but as
OCMock makes unit testing very easy even without explicit dependency injection, most of our Objective C dependencies were implicit. In Swift however, dynamic mocking libraries like OCMock do not exist. In Swift the only way to write testable code is by making dependencies explicit and injectable. Once this is done, you have to write your own mocks to verify behavior.
Sticking with the previous example: in Swift it would need to be changed, so the
viewModel can be passed in as a dependency to the `view’. This can be done by either having the
viewModel implement a protocol, or by subclassing the
viewModel itself. The test class needs to define the mock object that is being passed:
The Swift test code is visibly more verbose then the Objective version. However, the explicit dependency injection pattern forced us to decouple our code as much as we could. Before migrating to Swift, we thought that our Objective C code was pretty decoupled. After writing a couple of weeks of Swift code, the difference between the ‘old’ and ‘new’ code was pretty stark though! Moving to Swift — and testing our code properly — made our codebase more loosely coupled then before.
Dive Deep in the Good Parts
After getting the hang of dependency injection and writing our own mocks, we got much deeper into Swift, and started to pick up some really neat techniques. In the previous example I illustrated how to re-create the
OCMPartialMock functionality from Objective C. A cleaner approach would be to use pure mocks instead of partial mocks. In Swift a better way to write loosely coupled code is using protocols, and protocol oriented programming techniques. We picked up this really quickly and our code became more loosely coupled and more testable.
Then there’s some new language features like the guard and defer, generics, error handling with do-catch, nested types, the where clause and the @testable keyword — and and this is only touching the surface. Even though Swift is easy to get started with, there is plenty of depth to the language.
Apart from learning a new language, what else did we get out of moving over to Swift?
- Easier to read code:
- Stricter compile time checks compared to Objective C. Apart from type safety and the compile time benefits with this, the Swift compiler does additional checks like not allowing single line if statements or enforcing exhaustive switch statements
- No more header files — and no more and copying method declarations between
- … and of course the thrill of learning and using new language features!
On the disadvantages side: there seem to be surprisingly few. One important one is that some of our third party dependencies building on the dynamic nature of Objective C like JSONModel aren’t and won’t be available in Swift. And the other big one is that now need to maintain our existing Objective C code, which means additional context switching — and motivation to continuously transform more of our Objective C code to Swift.
Of course Swift is still a new language that is heavily under development, with breaking changes coming late 2016. Despite all that, our team agrees that moving our Objective C to Swift project has been a great success. It resulted in cleaner architecture, easier to read code and more productivity then if we’d have stayed all Objective C. More importantly: by undertaking a gradual change and not rewriting our ‘old’ code, shifting from Objective C to Swift has not slown us down one bit.
We’re also in your mail box
Sign up for our Code Voyagers newsletter where we’ll be soon sending learnings shared from our successes and failures and vacancies in our Engineering Tribe all over our global offices.
Work with us
If you would like to work with people open to trying new things, take a look at our current Skyscanner Engineering job roles. If you want to fast track your application do email a copy of your CV to codevoyagers (at) skyscanner.net quoting this article and giving one reason why you want to work at Skyscanner and we’ll fast track your application.
Originally published at codevoyagers.com on February 9, 2016.