42

数据序列化框架在 Swift 日常开发中的应用

 5 years ago
source link: https://blog.yuhanle.com/2018/07/05/json-analysis-in-swift/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

NBfYRfB.png!web

到了 Swift 年代,第三方库 SwiftyJSON 和 ObjectMapper 都曾经作为 JSON 转换的中流砥柱,只是这两者还是免不了“手动指定字段和JSON字典映射关系”的工作。于是阿里想了个黑科技( HandyJSON ),通过分析Swift数据结构在内存中的布局,自动分析出映射关系,进一步降低开发者使用的成本。

如今我们就有多个选择:ObjectMapper、HandyJSON、SwiftyJSON、MJExtension 等

其实我们在日常开发中,对于 JSON 数据的处理有两大需求:

  1. json 和 model 互相转换(Android Studio有 Gson format 插件,但Xcode没有类似功能)
  2. 服务端返回的 json 里可能有 null,但是 Swift 语言的空是用 nil 表示,需要空值处理(对象 Optional 类型)

框架简介

ObjectMapper

先看 ObjectMapper : Model 类必须实现 Mappable 协议,即实现 init 和 mapping 函数;适合跟 Alamofire 配合。但是 mapping 函数实现起来过于臃肿耗时,只能借助 插件 来快速完成。

import ObjectMapper

class PersonOBM: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var sex: Bool!
    var location: String?
    var three_day_forecast: [ForecastOBM]?
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        sex         <- map["sex"]
        location    <- map["location"]
        three_day_forecast <- map ["three_day_forecast"]
    }
}

class ForecastOBM: Mappable {
    var conditions: String?
    var day: String?
    var temperature: Double!
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        conditions      <- map["conditions"]
        day             <- map["day"]
        temperature     <- map["temperature"]
    }
}

j2s 是一个 macOS app 能够将 JSON 对象转成 Swift 结构体

HandyJSON

再看 HandyJSON , 写起来比较方便,类和结构体要求继承于 HandyJSON、枚举要继承于 HandyJSONEnum。

import HandyJSON

class PersonHJ: HandyJSON {
    var username: String?
    var age: Int?
    var weight: Double!
    var sex: Bool!
    var locatoin: String?
    var three_day_forecast: [ForecastHJ]?
    
    required init() {
        
    }
}

class ForecastHJ: HandyJSON {
    var conditions: String?
    var day: String?
    var temperature: Double!
    
    required init() {
        
    }
}

比ObjectMapper使用上要简单, 不用写mapping函数那么多代码了。

SwiftyJSON

SwiftyJSON :取字段值使用比较方便, 但是然并卵? SwiftyJSON 不支持转 Model,如果你只是想要解析某几个字段,那么 SwiftyJSON 是不二选择, 而且适用于 Alamofire。

let jsonString: String = "{\"username\":\"yuhanle\",\"age\":18,\"weight\":65.4,\"sex\":1,\"location\":\"Toronto, Canada\",\"three_day_forecast\":[{\"conditions\":\"Partly cloudy\",\"day\":\"Monday\",\"temperature\":20},{\"conditions\":\"Showers\",\"day\":\"Tuesday\",\"temperature\":22},{\"conditions\":\"Sunny\",\"day\":\"Wednesday\",\"temperature\":28}]}"
        
let dataFromString = jsonString.data(using: .utf8, allowLossyConversion: false)
do {
    let json = try JSON(data: dataFromString!)
    print(json["username"], json["weight"], json["three_day_forecast"][0]["conditions"])
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

MJExtension

最后看下一下 MJExtension ,作为一个从 ObjC 年代就开始流程的转换框架,在如今使用的人仍然很多,但是对于 Swift 的集成却不是特别友好,官方 issue 列表中经常都会有申请支持 swift 的呼声!

import MJExtension

class PersonMJ: NSObject {
    @objc var username: String?
    @objc var age = 0
    @objc var weight = 0.0
    @objc var sex = false
    @objc var location: String?
    @objc var three_day_forecast: [ForecastMJ]?
}

class ForecastMJ: NSObject {
    @objc var conditions: String?
    @objc var day: String?
    @objc var temperature = 0.0
}

尽管在支持上不是特别友好,但是在自定义 Model 的过程中,应该是最轻松的一款,但是在升级 Swift 4 之后,需要在属性前添加 @objc 才可以正常使用,否则转换失败。具体情况可参考: Swift 4 字典转模型失败

另外还有一种情况,就是关于整型属性,需要给定初试值,也就是说,MJExtension 无法序列化/反序列化整型。解决办法很简单, 就是赋个默认值, 即将Optional整型变为整型就可以。

class People: NSObject {
    var name: String?
    var age: Int?   // 请注意:MJExtension不能解析Optional Int类型
    var age = 0 	// 正确
}

运行耗时

我们准备了一小段 JSON 数据,来分析几大框架的运行耗时:

{
    "username": "yuhanle",
    "age": 18,
    "weight": 65.4,
    "sex": 1,
    "location": "Toronto, Canada",
    "three_day_forecast": [
        {
            "conditions": "Partly cloudy",
            "day": "Monday",
            "temperature": 20
        },
        {
            "conditions": "Showers",
            "day": "Tuesday",
            "temperature": 22
        },
        {
            "conditions": "Sunny",
            "day": "Wednesday",
            "temperature": 28
        }
    ]
}

以 HandyJSON 为例,在开始处理数据和结束时,记录时间差,时间差的结果每次会有波动

func testHandyJSON(json: String) -> Void {
    var start = CFAbsoluteTimeGetCurrent()

    var people: PersonHJ = PersonHJ()
    for _ in 0..<maxCount {
        people = PersonHJ.deserialize(from: json)!
    }

    var executionTime = CFAbsoluteTimeGetCurrent() - start
    print("HandyJSON deserialize time totals: ", executionTime)

    start = CFAbsoluteTimeGetCurrent()

    var res = ""
    for _ in 0..<maxCount {
        res = people.toJSONString()!
    }

    executionTime = CFAbsoluteTimeGetCurrent() - start
    print(res)
    print("HandyJSON toJSONString time totals: ", executionTime)
}

最终记录得到的结果对比图

测试项 JSON -> MODEL MODEL -> JSON HandyJSON 3.0839329957962 4.97446703910828 ObjeceMapper 1.40153098106384 1.2123589515686 SwiftJSON 不支持 不支持 MJExtension 0.417935013771057 0.418874025344849

6nmaMzU.png!web

结果有点出乎意料,HandyJSON 的黑魔法纵然很强大,这也导致了耗时的问题,相比较而言,不太友好的 MJExtension 速度最快。

总结

对于应用开发来说,JSON 数据序列化和反序列号的操作必不可少,上述分析的效率和性能问题,也应该多考虑,选择合适的框架很重要,学习和踩坑也是并存的。

另外,Swift 支持 Codable 协议,对这个需求的处理也有很大的支持!

参考链接


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK