It's time to blame! Experience migrating from Objective-C to Swift
Oleg Alekseenko, head of iOS development for Superjob, talks about the company's experience in moving from Objective-C to Swift.
The article is based on a speech at RIT2017.
Superjob 3 has iOS mobile apps:

Applications are downloaded more than 3 thousand times a day, the number of active users per day is more than 250 thousand.
The first versions of the applications were developed in 2012. All applications were originally written in Objective-C. After 5 years, a new Swift language has been released, we decided to switch to it. What did we want to achieve?
Raise Nullability
At the start - less than 5%. Inability to start the transition here and now. At the first attempt to implement Swift, there were many places in the application where Objective-C told us about the existence of the object, but in fact there was no object in run-time. When we tried to pass such an object to Swift, the application crashed.

How to decide:
What they achieved: after three months, Nullability reached 60%. This is the threshold from which to start the transition.
Result: in 3 months we improved the quality of the code, the application stopped writing when writing code on Swift. Laid the system for further development of applications on Swift.
We migrate from objc libraries in advance.
We use CocoaPods as a dependency manager. We had libraries connected through it. They can be divided into two categories:
In our case, the project had a ReactiveCocoa library.
We identified the decision criteria:
• To make it easy to use.
• That there was a strict typing.
• Swift like API.
As a result, we chose RxSwift.
As a solution, we wrote a category on RACSignal , which turns untyped ReactiveCocoa signals into typed Observable RxSwfit. First steps: create an Observable and subscribe to the new RACSignal values. When receiving new data in RACSignal, we try to cast it to the type that we specified in generic using convertBlock (we will discuss it a bit later). If it turns out, then forward the new typed value further to Observable subscribers. And if not, then we report an error of conversion to the desired type.
Further, we already have a public method that calls the Observable creation function internally and passes in the closure values from RACSignal, which are cast to the required type specified in generic.
Such a solution is well suited for standard types and collections, for example, NSArray easily fits into swift Array, NSNumber in swift, but ReactiveCocoa has a data structure such as RACTuple. There was a problem with it, since it simply does not work out to save it to carthage, so I had to write a separate method specifically for RACTuple that decompresses each value from RACTuple and collects carthage from them.
And like the kernel itself, a function has been made to cast an untyped value to a typed one.
We pass to the input of the function any value that has come to us from RACSignal, and the necessary type to which we need to be converted. If it turns out to cast the value immediately to a type, then the value itself returns, if not, the second step is to check whether the type we are trying to cast to is optional. If so, then create a variable of this optional type with an empty value. The last manipulations are needed, because if you do not create an optional variable, but simply return nil, the compiler will say that it cannot cast nil to the type T we need.
Now you can call the rxMap function of RACSignal and pass the necessary type, which we expect in the block subscribe, and from now on onNext we will always get the user model
We need to make it even more convenient and write extensions on the facade itself
We immediately show in it that we are returning an Observable, but inside we simply call rxMap (), and in this case we do not need to specify which type we need to cast to. The type itself is pulled from the return value.
As a result, we get rid of the need to cast types every time, and we do it only 1 time
Objective just doesn't let go.
A large amount of code in an existing application cannot be replaced right away. This leads to a problem: not all Swift features are available in Objective-C.
What exactly is not available from what we need to use:
The solution is Sourcery.
This solution is able to auto-generate code.
It’s easier to understand this using an example: we have a Resume structure that must satisfy the Hashable, Equatable protocols. But if you implement them yourself, you always have to remember that you must not forget to take into account the new property. You can trust all this to do Sourcery. To do this, we indicate that our struct Resume satisfies the two protocols AutoHashable and AutoEquatable.
These protocols themselves are nothing of the kind.
They are simply needed so that Sourcery understands which template to use for a particular structure.
Now you can run Sourcery. We get a file in which the implementation of the Hashable and Equatable protocols for Resume is automatically generated. If you embed sourcery in the build phase, you don’t have to worry that by adding new properties to our resume, we will forget to take them into account.
Hashbale and Equtable auto-generation templates come out of the box, but this does not limit us, since we can write a template for our needs on our own. For example, we have such an enum.
We want to build some kind of logic on the number of enums in enum, for this we can write a template and pass it to Sourcery.
In this template, we scan all found types. If it is enum, then create an extension for it, in which we declare a static variable with quantity.
Therefore, we took this opportunity to write our own templates to port the struct to Objective-c. We had to use such a trick so that in those places that have not yet been rewritten on Swift, we can work with resumes. As a result, we automatically generate the class ResumeObjc from our structure, which we can use in the old Objective-c code.

Using Mocks for Tests as an Example
When writing tests in Objective-c, we often used swizzling for mocks. But in Swift this is not possible, so I had to create some kind of “FakeProtocolClass” and there implement all the necessary methods, add specially additional variables that indicate whether the method was called or not. Sourcery can again come to the rescue, which automatically generates such mokas.

Upgrade team
Over the past six months, three out of four candidates for interviews at Superjob spoke of a desire to work specifically on Swift.
When switching to Swift, it was important to consider organizational issues in the team, such as code style and working with resources. The team has been working on Objective-C for many years, and about Swift, everyone had their own vision. Therefore, we needed a tool that would help direct the team in the right direction. One of Swift's most famous codestyle tools is SwiftLint.
SwiftLint allows you to enter your own rules. This helped us to register the mistakes specific to our team and quickly get rid of them. For example, we wrote a rule that prohibits the use of ReactiveCocoa in Swfit.

We also wanted to unify work with graphics, as the project survived several redesigns. SwiftGen helped with this: when deleting an icon, it tells you where it was used.
The article is based on a speech at RIT2017.
Superjob 3 has iOS mobile apps:
- Job search (creation of resume, search for vacancies, etc.)
- Search for employees (job creation, resume search)
- Production calendar (work and vacation planning)

Applications are downloaded more than 3 thousand times a day, the number of active users per day is more than 250 thousand.
The first versions of the applications were developed in 2012. All applications were originally written in Objective-C. After 5 years, a new Swift language has been released, we decided to switch to it. What did we want to achieve?
- Improve code predictability. The application has a filter model for 20 parameters, and this model should implement the method (to be compared). Many places where changes are possible. This all brings a lot of pain to the business logic, since when adding new properties everything should be taken into account in all sections of the application. On Objective-C, you need to follow this up with your hands, check all 100,500 places - the probability of an error increases. Swift makes such a situation impossible in principle.
- Migrate from objc libraries in advance. New libraries and UI components are written in Swift. Older are not supported. For example: the situation with Reactive Cocoa. If you don’t go today, then in five years we will have a dead piece in the application.
- To increase the effectiveness of the team and the stability of the development of the project: we have become more attractive to new employees, adopted internal standards of work quality. 70% of the new candidates for the Superjob development team wanted to write specifically on Swift.
What specific steps in our case had to be made for the transition
Raise Nullability
At the start - less than 5%. Inability to start the transition here and now. At the first attempt to implement Swift, there were many places in the application where Objective-C told us about the existence of the object, but in fact there was no object in run-time. When we tried to pass such an object to Swift, the application crashed.

How to decide:
- 3 months for each modified file added Nullability.
- We wrote a script that prevents Pull Request without labels.
What they achieved: after three months, Nullability reached 60%. This is the threshold from which to start the transition.
Result: in 3 months we improved the quality of the code, the application stopped writing when writing code on Swift. Laid the system for further development of applications on Swift.
We migrate from objc libraries in advance.
We use CocoaPods as a dependency manager. We had libraries connected through it. They can be divided into two categories:
- Those that are easily replaced with a Swift-like.
- Those that simply did not change, but required writing a migration or selecting an analogue (followed by writing a migration) for this fucking analogue.

In our case, the project had a ReactiveCocoa library.
- There is no understanding what you get. For example, this is how the call of the ReactiveCocoa method in Swift looks like
And the problem is just inprofileFacade.authorize(withLogin: login, password: pass).doNext(block: ((Any?) -> Void)!)since there is no idea what can come here.(Any?) - And therefore, it is necessary to cast to the right type each time and constantly remember why
profileFacade .authorize(withLogin: login, password: pass) .subscribeNext { (response) inif let user = response as? SJAProfileModel { print("\(String(describing: user.name))") } }
We identified the decision criteria:
• To make it easy to use.
• That there was a strict typing.
• Swift like API.
As a result, we chose RxSwift.
How we made ReactiveCocoa friends with RxSwfit
As a solution, we wrote a category on RACSignal , which turns untyped ReactiveCocoa signals into typed Observable RxSwfit. First steps: create an Observable and subscribe to the new RACSignal values. When receiving new data in RACSignal, we try to cast it to the type that we specified in generic using convertBlock (we will discuss it a bit later). If it turns out, then forward the new typed value further to Observable subscribers. And if not, then we report an error of conversion to the desired type.
extensionRACSignal{
privatefuncrxMapBody<T>(convertBlock: @escaping (Any?) -> T?) -> Observable<T> {
returnObservable.create() { observer inself.subscribeNext(
{ anyValue iniflet converted = convertBlock(anyValue) {
observer.onNext(converted)
} else {
observer.onError(RxCastError.cannotConvertTypes)
}
},
...
...
...
returnDisposables.create() {
}
}
}Further, we already have a public method that calls the Observable creation function internally and passes in the closure values from RACSignal, which are cast to the required type specified in generic.
extensionRACSignal{
publicfuncrxMap<T>(_ type: T.Type = T.self) -> Observable<T> {
return rxMapBody() { anyValue iniflet value: T = rx_cast(anyValue) {
return value
} else {
returnnil
}
}
}
}Such a solution is well suited for standard types and collections, for example, NSArray easily fits into swift Array, NSNumber in swift, but ReactiveCocoa has a data structure such as RACTuple. There was a problem with it, since it simply does not work out to save it to carthage, so I had to write a separate method specifically for RACTuple that decompresses each value from RACTuple and collects carthage from them.
publicfuncrxMapTuple<Q, W>(_ type: (Q, W).Type = (Q, W).self) -> Observable<(Q, W)> {
return rxMapBody() { anyValue iniflet convertible = anyValue as? RACTuple,
let value: (Q, W) = convertible.rx_convertTuple()
{
return value
} else {
returnnil
}
}
}And like the kernel itself, a function has been made to cast an untyped value to a typed one.
internalfuncrx_cast<T>(_ value: Any?) -> T? {
iflet v = value as? T {
return v
} elseifletE = T.selfas? ExpressibleByNilLiteral.Type {
returnE.init(nilLiteral: ()) as? T
}
returnnil
}We pass to the input of the function any value that has come to us from RACSignal, and the necessary type to which we need to be converted. If it turns out to cast the value immediately to a type, then the value itself returns, if not, the second step is to check whether the type we are trying to cast to is optional. If so, then create a variable of this optional type with an empty value. The last manipulations are needed, because if you do not create an optional variable, but simply return nil, the compiler will say that it cannot cast nil to the type T we need.
Now you can call the rxMap function of RACSignal and pass the necessary type, which we expect in the block subscribe, and from now on onNext we will always get the user model
profileFacade
.authorize(withLogin: login, password: pass)
.rxMap(SJAProfileModel.self)
.subscribe(onNext: { (user) in
})
.addDisposableTo(disposeBag)We need to make it even more convenient and write extensions on the facade itself
extensionSJAProfileFacade{
funcauthorize(login: String, passwrod: String) -> Observable<SJAProfileModel> {
returnself.authorize(withLogin: login, password: passwrod).rxMap()
}
}We immediately show in it that we are returning an Observable, but inside we simply call rxMap (), and in this case we do not need to specify which type we need to cast to. The type itself is pulled from the return value.
As a result, we get rid of the need to cast types every time, and we do it only 1 time
profileFacade
.authorize(login: login, password: pass)
.subscribe(onNext: { (user) in
})
.addDisposableTo(disposeBag)Objective just doesn't let go.
A large amount of code in an existing application cannot be replaced right away. This leads to a problem: not all Swift features are available in Objective-C.
What exactly is not available from what we need to use:
- Struct
- Enum
- Moki
The solution is Sourcery.
This solution is able to auto-generate code.
It’s easier to understand this using an example: we have a Resume structure that must satisfy the Hashable, Equatable protocols. But if you implement them yourself, you always have to remember that you must not forget to take into account the new property. You can trust all this to do Sourcery. To do this, we indicate that our struct Resume satisfies the two protocols AutoHashable and AutoEquatable.
structResume:
AutoHashable,
AutoEquatable{
var key: Int?let name: String?var firstName: String?var lastName: String?var middleName: String?var birthDate: Date?
}These protocols themselves are nothing of the kind.
protocolAuthoHashable{}
protocolAutoEqutable{ }
They are simply needed so that Sourcery understands which template to use for a particular structure.
Now you can run Sourcery. We get a file in which the implementation of the Hashable and Equatable protocols for Resume is automatically generated. If you embed sourcery in the build phase, you don’t have to worry that by adding new properties to our resume, we will forget to take them into account.
extensionResume: Hashable{
internalvar hashValue: Int {
return combineHashes([key?.hashValue ?? 0,
name?.hashValue ?? 0,
firstName?.hashValue ?? 0,
lastName?.hashValue ?? 0,
middleName?.hashValue ?? 0,
birthDate?.hashValue ?? 0, 0])
}
}
extensionResume: Equatable{}
internalfunc == (lhs: Resume, rhs: Resume) -> Bool {
guard compareOptionals(lhs: lhs.key,
rhs: rhs.key,
compare: ==) else { returnfalse }
guard compareOptionals(lhs: lhs.name,
rhs: rhs.name,
compare: ==) else { returnfalse }
guard compareOptionals(lhs: lhs.firstName,
rhs: rhs.firstName,
compare: ==) else { returnfalse }
...
...
returntrue
}Hashbale and Equtable auto-generation templates come out of the box, but this does not limit us, since we can write a template for our needs on our own. For example, we have such an enum.
enumConf{
caseAppscaseBackendcaseWebScalecaseAwesome
}We want to build some kind of logic on the number of enums in enum, for this we can write a template and pass it to Sourcery.
{% forenumintypes.enums %}
extension{{ enum.name }} {
staticvar numberOfCases:Int = {{ enum.cases.count}}
}
{% endfor %}In this template, we scan all found types. If it is enum, then create an extension for it, in which we declare a static variable with quantity.
extensionConf{
staticvar numberOfCases:Int = 4
}Therefore, we took this opportunity to write our own templates to port the struct to Objective-c. We had to use such a trick so that in those places that have not yet been rewritten on Swift, we can work with resumes. As a result, we automatically generate the class ResumeObjc from our structure, which we can use in the old Objective-c code.

Using Mocks for Tests as an Example
When writing tests in Objective-c, we often used swizzling for mocks. But in Swift this is not possible, so I had to create some kind of “FakeProtocolClass” and there implement all the necessary methods, add specially additional variables that indicate whether the method was called or not. Sourcery can again come to the rescue, which automatically generates such mokas.

Upgrade team
Over the past six months, three out of four candidates for interviews at Superjob spoke of a desire to work specifically on Swift.
When switching to Swift, it was important to consider organizational issues in the team, such as code style and working with resources. The team has been working on Objective-C for many years, and about Swift, everyone had their own vision. Therefore, we needed a tool that would help direct the team in the right direction. One of Swift's most famous codestyle tools is SwiftLint.
SwiftLint allows you to enter your own rules. This helped us to register the mistakes specific to our team and quickly get rid of them. For example, we wrote a rule that prohibits the use of ReactiveCocoa in Swfit.

We also wanted to unify work with graphics, as the project survived several redesigns. SwiftGen helped with this: when deleting an icon, it tells you where it was used.