We started developing Skyscanner TravelPro in Objective C in March 2015. When Swift 2.0 was released a few months later, we began to slowly adopt Swift. Eight months later –100% of the new code is written in Swift. All of this is done without rewriting existing, well-running, robust, tested Objective-C code — that doesn’t make sense anymore.

There is a lot of information out there on how to decide whether to use Swift in a new project, as well as Swift best practices. If you’re developing on a sizable Objective-C code base, you might find this article useful. If not — someday you might run into a project that you want to develop with Swift: this article offers advice on how to get started.

The visualization below shows the changes in our code base over a period of 10 months. Since November 2015, all new code has been written in Swift, which represents about 10% of our 65,000 line code base and is growing.

So how do we go from Objective-C to Swift?

1. Start with the easy part

We decided to start with the simplest parts possible, such as some separate classes that can test themselves and be called internally. Our initial choices were simple UI control code, utility functions, and extension methods for existing classes.

For example, in the original Swift component, we wrote a String extension method to make localized strings more comfortable to read:

extension String {
   var localized: String! {
      let localizedString = NSLocalizedString(self, comment: "")
      return localizedString
   }
}Copy the code

The interesting thing is we could have done the same thing with Objective-C categorization. However, our team will never use the old one againNSLocalizedString(@”MyText”, @””).manyNew ideas emerge with the use of a new language. So from the first day of development with Swift our Swift strings are written“MyText”.localizedFormat.

2. Call existing Objective-C code from Swift

After writing some separate Swift components and unit testing them, we started calling our existing Objective-C classes with Swift. It all starts to get real.

To call objective-C classes from Swift you need to define the Swift bridge connector file. This is a.h file that defines all objective-C headers that can be exposed to Swift calls. At the beginning of the file, you need to change the compilation Settings so that the compiler adds it at compile time. And when that’s done, these Objective-C classes are imported into Swift’s world and can be easily called by Swift.

When you call objective-C class in Swift, you may receive a warning: pointer is missing a Nullability type specifier. When Objective-C code is imported into Swift, the compiler checks for nullability compatibility– this prompt appears if there is no information about nullability. This check is done because nullability messages in Swift are explicitly declared, whether of non-null type or optional type.

All we need to do is add nullability information to the called Objective C header file to eliminate these compiler warnings. We use the new Nullable and nonNULL annotations to do this. The reset only took a few hours because we only needed to modify the classes we used in Swift, which amounted to a few hundred lines of code. However, while making these changes we struggled to figure out what could or could not be set to nil in the existing code base, but we had to make a clear choice when we exposed that code to Swift.

In most cases, refactoring involves changes like this:

@property (nonatomic, strong, readonly) THSession *session; // New Swift friendly method signature @property (nonatomic, strong, readOnly, nullable) THSession *session;Copy the code

With blocks in the method signature, the modification is a little more complicated, but nothing unmanageable:

// Sign the original method in the.h file - (NSURLSessionDataTask *)updateWithSuccess: (void(^)())success error:( void(^)(NSError * error))error; // New, Swift-friendly method signature - (nonNULL NSURLSessionDataTask *)updateWithSuccess: (nullable void(^)())success error:(nullable void(^)(nonnull NSError *error))error;Copy the code

3. Call Swift code in Objective C

After a number of moderately complex Swift components that use the Objective-C class, it’s time to call them in Objective C. Calling the Swift component from Objective-C is more straightforward because no additional bridge files are required.

The only changes we need to make to our existing Swift files are to inherit NSObject or add the @objc attribute to the Swift class we want to expose. There are some special Swift classes objective-C doesn’t work with, like Structures, tuples, and Generics, as well as other classes. These restrictions don’t matter because we don’t want to expose any new constructs to Objective-C. The only special case where we need to do some extra processing is enumeration types. To use the enumerated type defined in Swift, we need to explicitly declare the Int value type in Swift:

@objc enum FlightCabinClass: Int {
   case Economy = 1, PremiumEconomy, Business, First, PrivateJet
}Copy the code

4. Revisit unit testing and dependency injection from the Swift perspective

As we had more complex modules using dependencies, we ran into a problem with no clear solution: unit testing. Unlike Objective-C, Swift does not support read-write reflection. Simply put, there is no Swift equivalent to OCMock, and mocking frameworks do not exist.

Here’s an example that drives us crazy. We want to test that the saveTrip method can be called by the viewModel property of the View object when we click the submit button on a page. In Objective-C, using OCMock is very easy to test:

// When the submit button is clicked, A method is invoked on the ViewModel - (void) test_whenPressingSubmitButton_thenInvokesSaveTripOnViewModel {/ / given TripViewController *view = [TripViewController alloc] init]; id viewModelMock = OCMPartialMock(view.viewModel); OCMExpect([viewModelMock saveTrip]); // when [view.submitButton press]; // then OCMVerifyAll(viewModelMock); }Copy the code

This method does not work in Swift. Objective-c unit tests often rely on sophisticated simulation frameworks like OCMock. Dependency injection is a good practice, but because OCMock makes unit testing so easy that it doesn’t even require explicit dependency injection, most of our Objective-C dependencies are implicit. In Swift, dynamic emulation libraries like OCMock don’t exist. The only way to write testable code in Swift is to make dependencies explicit and injectable. Once this is resolved, you need to write your own mock objects to verify the behavior.

Revisit the previous example: in Swift you need to change the viewModel so that it can be passed as a view dependency. As long as the viewModel implements the protocol, or inherits the viewModel. The test class needs to define mock objects for passing:

func test_whenPressingSubmitButton_thenInvokesSaveTripOnViewModel() {
   // given
   let viewModelMock = TripViewModelMock()
   let view = TripViewController(viewModelMock)
 
   // when
   view.submitButton.press()
 
   // then
   XCTAssertEqual(viewModelMock.saveTripInvoked)
}
 
class TripViewModelMock: TripViewModel {
   var saveTripInvoked = false
 
   override func saveTrip() {
      self.saveTripInvoked = true
   }
}Copy the code

The Swift test code looks more verbose than the Objective C version. In fact, showing the dependency injection pattern makes us more concerned with minimizing the coupling of our code. Before moving to Swift, we thought our Objective-C code was poorly coupled. After a few weeks of writing Swift code, the differences between the ‘old’ code and the ‘new’ code became apparent. Moving to Swift and testing the code correctly made our code base less coupled.

get

After getting the hang of dependency injection and writing our own mock objects in this way, we dug deeper into Swift and started picking up technologies that no one else had touched on. In the previous example I showed how to recreate the OCMPartialMock function in Objective C. A clearer approach is to use pure mocks instead of partial Mocks. A better way to write flexible, low-coupling code in Swift is to use protocols and protocol-oriented programming techniques. We quickly chose this direction, and the code became less coupled and more testable.

New language will have new language features, like a guard and defer and generic generics, use the do – catch, nested types (nested types), the where clause and @ testable keyword for error handling (error handling), These are just the tip of the iceberg. Even so, Swift is easy to get started and has a lot to dig into.

Besides learning a new language, what else did we learn from moving to Swift?

More readable code:

// Objective C [self addConstraint:[NSLayoutConstraint constraintWithItem: myButton attribute: NSLayoutAttributeCenterX relatedBy: NSLayoutRelationEqual toItem: myView attribute: NSLayoutAttributeCenterX Multiplier: 1.0 constant: 0.0]]; Self. addConstraint(NSLayoutConstraint(item: myButton, attribute:.centerx, relatedBy:.equal, toItem: MyView, attribute:.centerx, Multiplier: 1.0, constant: 0.0)Copy the code

More stringent compile-time checking than objective-C. In addition to the type safety and compile time benefits, The Swift compiler also allows no hard single line if statements, such as if expressions, or forces switch expressions to list the situation fully (enforcing Bare switch) (statements) and other conditions for additional checks.

Get rid of the header file. And no longer need to switch between.h and.m files to copy method declarations.

Then there is the fear of learning and using new language features

The comparative advantages, the disadvantages are surprisingly few. One obvious inconvenience is that some of our third-party dependencies — like JSONModel– are built on objective-C dynamics, which doesn’t work on Swift. Another problem is that we need to maintain existing Objective-C code, which means additional environment transformations and incentives to keep converting more Objective-C code to Swift code.

Of course Swift is still a new language and there will be sensational changes later in 2016. Nevertheless, our team considered the project to convert our Objective-C code to Swift a success. It makes the structure cleaner, the code easier to read, and more productive than it was in objective-C development. More importantly: Transitioning from Objective-C to Swift didn’t slow us down at all, without making smooth changes and rewriting ‘old’ code.

Wechat scan

Subscribe to daily mobile development and APP promotion hot news public account: CocoaChina