Let’s imagine that we have a mature iOS app under active development. At some stage one of the team members comes up with a suggestion: “Why don’t we migrate to Swift?” Obviously you know that suspending feature development for 3 months and rewriting the entire app almost from scratch is not an option.

3 months ago at OLX we have embarked on a journey of migration. On the way we’ve encountered countless challenges and problems and now we feel that we want to share some tips and good practices that you may find helpful during future migrations.

How do I start?

First you need to identify the parts of the code you want to start with. The process will be smooth and easy if you just follow the rules:

File by file migration is the most effective way to go.

Start with files without subclasses — you cannot subclass a Swift class in Objective-C.

Remember: Objective-C will not be able to translate certain Swift-specific features

If there are parts of the code that you don’t want to migrate just yet (e.g. in-house pod) it is worth considering adopting Nullability to Objective C code. You will avoid ending up with a Swift code with implicitly unwrapped optionals. Each part that would work as nil in Objective-C will crash in Swift (accessed without necessary nil-check).

Mix-and-match functionality makes it easy to choose which features and functionality to implement in Swift, and which to leave in Objective-C. Interoperability makes it possible to integrate those features back into Objective-C code with no hassle.

This nice quote comes from a book that was already mentioned before: “Using Swift with Cocoa and Objective-C”. In the real world however, sometimes it works more like this:

Header file

Only classes and types included in the automatically generated Swift header file will be accessible in Objective-C code. They absolutely have to be stored in *.m files.

If both Swift and Objective-C classes are in the application project, use:

#import "ModuleName-Swift.h"

If both Swift and Objective-C classes are in the same Pod project, use:

#import <PodName/PodName-Swift.h>

If Swift file is in another Pod, use:

@import PodName;

(with DEFINES_MODULES and use_frameworks! enabled)

In Objective-C header files only use forward declarations for @class ClassName and @protocol ProtocolName . Importing a Swift header file there will leave you with a circular reference.

Protip: ⌘ + click on Swift class name to see its generated header

Swift class in Objective-C

You have already migrated the first class but fail to see it in Objective-C code? The property is not visible? The mixed projects have some limitations and you need to respect certain rules:

In order to be visible in Objective-C all classes should inherit from NSObject

In Objective-C primitive types cannot be nullable, so properties with Int? , Int! , Float? , … will not be visible

, , , … will not be visible Some names are already used in NSObject , like description: String

, like Swift classes have objc_subclassing_restricted property, so they cannot be subclassed in Objective-C

property, so they cannot be subclassed in Objective-C Methods with NS_REQUIRES_NIL_TERMINATION will not be visible

Swift enum in Objective-C

In Objective-C enum can only have Integer type, so all of our fancy Swift enums with associated values or String type will not be visible. Additionally, enum visible in Objective-C should have the @objc prefix.

What will NSCoder fail to see?

Unfortunately NSCoder is based on Objective-C runtime and as such will not see properties, which are not exposed to Objective-C (like enum: String , etc…). Instead, you will end up with a runtime error: “this class is not key value coding-compliant for the key status”. How can we deal with it?

It is possible to map property value for type known by NSCoder. In this example we are converting enum: String to it’s rawValue.

Using Enumerations as Errors

Representing all error reasons in Swift enums along with their associated values is a very convenient and recommended way to go.

It works well, unless we use these errors in Swift:

(lldb) po error

Optional<Error>

some : ServiceError

apiError : 3 elements

.0 : "User authorisation data are invalid"

.1 : Optional<String>

some : "Invalid username and password combination"

.2 : Optional<String>

some : "invalid_grant"

But when we try to figure out the failure reason in Objective-C it comes out… empty:

(lldb) po error

Error Domain=restapi_client.ServiceError Code=0 "(null)"

Fortunately in Swift 3 we are able to use CustomNSError protocol that describes the error type -provides a domain, code, and user-info dictionary.

Then in Objective-C we will have:

(lldb) po error

Error Domain=RestAPI.errorDomain Code=0 "User authorisation data are invalid" UserInfo={NSLocalizedDescription=User authorisation data are invalid, errorCode=invalid_grant} (lldb) po error.localizedDescription

User authorisation data are invalid

Optional(“2000 PLN”)

Before the release remember to double check all string interpolations in order not to end up with Optional("2000 PLN") visible for user.

In Swift 3.1 this raises a warning:

String interpolation produces a debug description for an optional value; did you mean to make this explicit?

Before that make sure that you have unwrapped all Optionals.

APNS token == “32bytes”

A very long time ago, back in the day when nobody even thought about Swift we have implemented Push Notifications with a solution that proved quite popular at the time:

We quickly realized the extent of our mistake when AppDelegate was migrated to Swift and all of the new tokens in the database were equal to “32bytes” string. Why did that happen? Because Swift Data description is equal to “ N bytes ”. How to parse it properly?

Iterate through data bytes array and concatenate as hex strings or… use deviceToken.toHexString() from Marcin Krzyżanowski’s CryptoSwift library.

Conclusion

After 3 months we have migrated about 40% of our code to Swift. It hasn’t been an easy ride but one that is absolutely worth it. Still, there are many drawbacks in Xcode 8 Swift support (slower code completion or disabled refactoring to name a few), but Swift makes up for it by enabling us to use powerful enums, protocol extensions and lazy variables. We are definitely ready to embrace all of the new features proposed by the Swift community!