一份很是详尽的 Objective-C 到 Swift 的迁移指南

原文在个人博客:一份很是详尽的 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 层

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 替换成 eTo(self. 替换成 To(mas_ 替换成 snp.); 替换成 )。老实说,这部分改写,是最轻松的😂

其余

在重写的过程当中,把 AppDelegate 改为 Swift 以后,发现再也不须要 main.m 了,查询资料得知这是正常的,@UIApplicationMain 帮咱们作了这件事情。

再就是有些函数能够用 extension 的方式写,能够写的很优雅。

总之,Swift 上面仍是有着不少 Cocoa 的影子,尽管他有不少新特性,在设计模式方面,跟 OC 差别不大,也可能我入门时间短,写法太 OC 化,因此若是有相似的,还请多多指正。

重写工做也没那么难,咱们的 app 虽然不大,其实也不小,有完整的用户模块,有购物模块,订单模块,支付模块,推送模块,几天时间就所有改写完毕,而且已经在测试,目前还没发现有很大问题。

最基础的模块先搭建,好比主题颜色管理,API 模块,一些工具类,基础框架搭好以后,由于 OC 代码能够被 Swift 调用,在开始的时候,作好计划,小的模块先调用 OC,避免一下重写不少模块,致使没有动力写下去。后面几乎全是体力活,就是时间问题了。

相关文章
相关标签/搜索