原文在個人博客:一份很是詳盡的 Objective-C 到 Swift 的遷移指南json
國際友人能夠看這裏:A guidebook for migrating from Objective-C to Swiftswift
運行環境:Xcode 9,Swift 4.0/4.1後端
按照慣例先說幾句廢話,Swift 在剛發佈的時候,我學過一點點,寫了幾行代碼,第二年發現之前的代碼不能跑了,就棄坑 Swift,再加上實習過的公司主要用 OC,確實沒機會系統的學一下 Swift,近來發現一些想要的第三方庫,都只提供 Swift 版本,以及一些其餘緣由,決定把公司的項目徹底用 Swift 改寫。設計模式
認識個人朋友可能知道,我在去年年末發過一篇文章,叫《從重構到吐血 - 我是如何刪掉 6 萬行代碼而且不刪減原有功能的》,當時花了幾周時間重構了全部代碼,三個項目。api
最近也同樣,花了三四天時間,重寫了其中一個項目,而且整理出來一些經驗。目前除了一些必須依賴的第三方庫好比 AliyunOSS,所有轉到 Swift 了,能夠說是 Almost Pure Swift。數組
若是寫太詳細的話,篇幅就太大了,因此有些地方會省略一點寫。bash
先大概列個提綱,我打算講講可選類型,重寫的順序,網絡層,數據層,UI 層。網絡
我認爲一門語言,語法奇怪不是很大問題,熟悉下就好,可是 Optional 類型是真的難理解,! ? ?? 這類符號傻傻看不懂,最開始解析個 json 處處都是 ?,再加上網上各類文檔,素質良莠不齊,越學越迷茫。app
Optional 類型很好理解,只是區分了下 nil 和 非 nil,若是這個 property 不必定存在,好比後端傳來的 json,有時候格式是空數組 [],有時候是 null,這兩種在語義上理解都是空,可是對 Swift 語言是徹底不一樣的。具體的我會在數據層詳細寫下。框架
最開始的打算是慢慢遷移到 Swift,先從最邊緣的模塊開始寫,UI 改版再重寫之前的代碼,後來越寫越上癮,感受找回了本科作項目的感受,通宵寫代碼,就索性所有重寫了。
還有一個緣由是寫着寫着發現有些通用的部分,和以前的 OC 代碼有關聯,新的模塊用 Swift 寫會有沒法混編的狀況,好比 Swift 的結構體,非繼承自 NSObject 的類,在 OC 沒法正經常使用。
總的順序仍是從邊緣到中心,先寫最邊緣的業務代碼,好比某個刷新列表,這個時候就要寫 Swift 的網絡層,數據層,這兩塊也能夠和 UI 層摻雜着寫。
Swift 的網絡層通常作法是用 Alamofire,咱們的 app 不算複雜,只是對 Alamofire 作一個封裝就夠了。最開始我執着於遵循 Alamofire 的鏈式調用,發現好雞兒難,而後驚喜的發現 Swift 也有 block,因而用了模仿 OC 網絡層封裝的方式,作一個單例,封裝下 request 方法。
單例的寫法,有好幾種,篇幅限制,我直接貼出最佳實踐,至少是 Swift 4.0/4.1 的最佳實踐。
class APIService {
static let shared = APIService()
}
複製代碼
而後就能夠往裏面添枝加葉了,好比在 init()
方法設置一些網絡狀態監控,一些通用的設置,我就貼一個精簡版的,而後能夠按照官方文檔,寫一個 AccessTokenAdapter
,用來處理頭部的受權信息。
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
}()
複製代碼
在有些開源項目裏面,網絡層分層過於詳細,url 封一層,每一個請求寫一個函數,並且寫的還賊雞兒醜,仍然一大堆重複代碼,重複的 hard code 字符串,不知道這種封裝的意義何在,每加幾個 api,要新建一個類,而後寫 url 層,再寫每一個 request 的函數,封這麼多層,仍然處處可見字符串硬編碼,還都是重複的。
關於 request 的封裝,我作了很是基礎的封裝,畢竟 app 沒那麼複雜,Alamofire 的部分太長了,大概思路就是根據傳過來的參數,設置請求的序列化方式,設置 headers,設置參數等等,爲了方便一些不須要傳參的 get 方法,我作了這麼一個操做:
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)
}
複製代碼
調用的時候大概就是這樣:
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?
是爲了防止後端返回 null 而不是 [],若是真返回了 null,??
的做用是給 array 一個默認值,保證 array 必定是 Array 類型,而不是 Optional,方便後面的解析。
Swift 在結構體方面真是強大了太多了,篇幅關係不寫那麼多,Swift 4 引入了一個原生 json 轉模型的方法,並且我還發現一個國人,翻譯了老外的文章,不註明原地址,當原創了。
原文:Ultimate Guide to JSON Parsing with Swift 4
原文寫的很詳細,代碼再也不貼了,須要注意的是,若是後端返回數據不夠規範,多用幾個 ?
避免 crash。
一樣的,數據放在數據層處理,善用計算屬性,舉個例子
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 層實際上是最簡單的,lazy load 直接用 lazy var get 重寫,Masonry 佈局代碼能夠很方便的轉成 SnapKit 代碼,UIKit 框架的代碼直接翻譯便可。
[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);
}];
複製代碼
方法名用 copy paste 解決,而後開啓編輯器的替換功能,將 mas_e
替換成 e
,To(self.
替換成 To(
,mas_
替換成 snp.
,);
替換成 )
。老實說,這部分改寫,是最輕鬆的😂
在重寫的過程當中,把 AppDelegate 改爲 Swift 以後,發現再也不須要 main.m 了,查詢資料得知這是正常的,@UIApplicationMain
幫咱們作了這件事情。
再就是有些函數能夠用 extension 的方式寫,能夠寫的很優雅。
總之,Swift 上面仍是有着不少 Cocoa 的影子,儘管他有不少新特性,在設計模式方面,跟 OC 差別不大,也可能我入門時間短,寫法太 OC 化,因此若是有相似的,還請多多指正。
重寫工做也沒那麼難,咱們的 app 雖然不大,其實也不小,有完整的用戶模塊,有購物模塊,訂單模塊,支付模塊,推送模塊,幾天時間就所有改寫完畢,而且已經在測試,目前還沒發現有很大問題。
最基礎的模塊先搭建,好比主題顏色管理,API 模塊,一些工具類,基礎框架搭好以後,由於 OC 代碼能夠被 Swift 調用,在開始的時候,作好計劃,小的模塊先調用 OC,避免一下重寫不少模塊,致使沒有動力寫下去。後面幾乎全是體力活,就是時間問題了。