Table of Content

Requirements: Xcode 9, Swift 4.0/4.1

In 2014, when Swift 1.1 was released, I read the official documents, and wrote a demo, at that time, my personal project was under development and it needed to be released as soon as possible, therefore, I didn't rewrite it in Swift. And all the companies which I worked for were using Objective-C, which resulted the lack of chance for me to write a Swift application for commercial usage. Today, I have realized Swift is becoming more and more popular, and many companies have migrated their projects to Swift, then I made a fancy decision, rewriting our project in Swift.

If you ever paid attention to me, you may know that I published an article named <How did I maintain all the function with a deletion of 60,000 lines of code>, I refactored all the three projects in a few weeks.

Today, I have rewrote one of them in Swift, the task cost me 3-4 days, and I'm sharing some experience about the migration work.

I'm going to talk about Optional Type, the scheme of migration, Network layer, Data model layer, and the UI layer.

Optional Type

A new language, if it's grammar or expression is extremely different from other languages, most of programmers can adapt to it in a short time, but wtf is Optional Type... What does ! mean, and also ?, and ??, the first time I tried Swift, I did a lot of additional work on JSON parsing, a lot of if-else statements, a lot of ! to force unwrapping variables.

And many documentations can not explain that thoroughly, actually, Optional Type can be understood easily, if a variable was declared with Optional, it can be assigned a nil object, for example, a response from API server, we want a list of something, sometimes there are no records in the database. Alright, some back-end programmers return an empty array to clients, it looks like this: {errorCode: 0, body: []}, some programmers return a null object to clients, looks like this: {errorCode: 0, body: null}.

The problem is, despite null and [] all represent that there are no items, for Swift language, null means nil object while [] is Array Type. They are totally different, I will explain that in the Data model layer chapter.

Scheme of migration

At first, I planned to migrate it to Swift gradually, later I found that some common modules can not be called by OC and Swift simultaneously, such as Struct written in Swift, and some classes which aren't subclasses of NSObject. Finally, I decided to rewrite all the modules.

In general, we should do the migration work little by little, or we will feel frustrated. First, we can rewrite an independent page, such as an event list, during the migration, we need to design API layer and Data model layer firstly.

Network layer

A universal solution is using Alamofire, my app is not complex, so I wrapped Alamofire in an APIService singleton.

There are many ways to implement a singleton, I'll give the best practice, at least in Swift 4.0/4.1.

class APIService {
    
    static let shared = APIService()
}

Then we can add features to it, set up the network monitor in init() method, and some universal configuration, or for advance usages, you can write an AccessTokenAdapter by following the official documents to deal with HTTP headers. I'll give a simple example.

lazy var sManager: SessionManager = {
    let l = (UserDefaults.standard.object(forKey: "AppleLanguages") as! Array<String>)[0]

    var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
    defaultHeaders["User-Agent"] = "Customized UA"
    defaultHeaders["Accept-Language"] = "\(l),en;q=0.8"
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = defaultHeaders
    let sManager = Alamofire.SessionManager(configuration: configuration)

    return sManager
}()

In some open-sourced project, the network layer has been designed too complex, including a URL layer, and a function for every request, looked ugly, there were many duplicated code, even duplicated hard code strings. Besides, every time you add a new API, you are required to add a new class, and implement the url getter method, and wrap a function for every request. I don't think it makes sense, for security reasons? MITM attack can capture all the network traffic, and those strings can also be revealed by analyzing the binary file in the Reverse Engineering field. It just brings useless work to programmers.

About the wrapping of request function, I did some routine action, for some simple non-params get request, I wrote a function, to simplify the call methods:

func request(path: String, success: ((Any) -> Void)?, failure: ((ErrorModel) -> Void)?) {
    request(method: .get, path: path, params: nil, paramsType: nil, requireAuth: true, success: success, failure: failure)
}

func request(method: HTTPMethod, path: String, params: [String: Any]?, paramsType: ParamsType?, success: ((Any) -> Void)?, failure: ((ErrorModel) -> Void)?) {
    request(method: method, path: path, params: params, paramsType: paramsType, requireAuth: true, success: success, failure: failure)
}

It looks like this when it is called:

APIService.shared.request(path: "/get/some-list/api", success: { (data) in
    
    let array = data as? [[String: Any]] ?? []
    let data = try! JSONSerialization.data(withJSONObject: array, options: [])

    guard let items = try? JSONDecoder().decode([ItemModel].self, from: data) else {
        return
    }
    
    tableView.reloadData()
}) { (error) in
    
}

as? keyword can prevent the crash if value from back-end is null instead if [], using as! will cause a crash because null is not Array Type.

Assuming there is a null object from back-end, ?? keyword can assign [] to the array variable, to make sure the value is always an array.

Data model layer

Struct in swift has been endowed with a lot of new features, but I'm not going to write too much about that. The point is, Swift 4 has finally answered the question of how to parse JSON with Swift.

You can find more details in this article: Ultimate Guide to JSON Parsing with Swift 4

The writer has thoroughly explained the Codable feature. There are still some points that we should be aware of, if the APIs you are using are not in the standard, use some ? to prevent crash.

Additionally, data should be processed in Data layer, computed properties can be used to deal with data. For example:

struct ActivityModel : Codable {
    
    let createTime: Date
    
    var createTimeString: String {
        return createTime.formattedString(withDateFormat: "yyyy-MM-dd")
    }
}

struct OrderModel : Codable {
    
    let currency: String
    let status: OrderStatus

    var statusString: String {
        switch status {
        case .deleted:
            return "Deleted"
        case .created:
            return "Created"
        case .paid:
            return "Completed"
        case .cancelled:
            return "Cancelled"
        }
    }
}

UI layer

The migration of UI layer is the easiest one, lazy load variables can be rewritten in lazy var get, the layout code of Masonry can be easily translated into SnapKit, and most of the UIKit code looks similar to Objective-C.

[aView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(self);
    make.bottom.mas_equalTo(self).offset(-5);
    make.leading.mas_equalTo(self);
    make.trailing.mas_equalTo(self);
}];

[bView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(self.aView.mas_bottom);
    make.leading.mas_equalTo(self.aView).offset(12);
    make.height.mas_equalTo(45);
    make.width.mas_equalTo(45);
}];

Function name can be duplicated by using copy-paste, then replace mas_e, To(self., mas_, ); with e, To(, snp., ) respectively. I think this part is the most convenient part of the migration.

Others

During the migration, I found that Swift projects do not need main.m any more, it is reasonable because @UIApplicationMain has done that work for us.

And the extension feature of Swift is so powerful, we can implement some functions in a graceful way.

Despite Swift has more features than OC, Apple has not rewritten the Cocoa framework for Swift, Swift projects are still very similar to Objective-C projects.

Migration work is not as hard as we thought, my project is in medium-scale, including user module, shopping module, order module, payment module, APNS module, and they are migrated in several days, and I have arranged test efforts, there aren't many issues at present, quite stable.

In summary, build the foundation modules first, such as theme color management, API layer, and some common utils. OC code can be called by Swift smoothly, so, at first, do not wonder refactoring all classes at a time, some branch line classes migration tasks should be put at the end. When those fundamental modules are ready, the migration task will transform into physical work, the only requirement is time.