Swift JSON与模型转换(HandyJSON封装)


https://www.jianshu.com/p/25db8030dba2

 

2018.05.02 20:20:16字数 1,493阅读 5,017
一 简介
二 特性
三 安装使用以及封装
四 使用示例
五 总结
一 简介

HandyJSON是一个用于Swift语言中的JSON序列化/反序列化库。

与其他流行的Swift JSON库相比,HandyJSON的特点是,它支持纯swift类,使用也简单。它反序列化时(把JSON转换为Model)不要求Model从NSObject继承(因为它不是基于KVC机制),也不要求你为Model定义一个Mapping函数。只要你定义好Model类,声明它服从HandyJSON协议,HandyJSON就能自行以各个属性的属性名为Key,从JSON串中解析值。

HandyJSON目前依赖于从Swift Runtime源码中推断的内存规则,任何变动我们将随时跟进。

二 特性
  • 序列化Model到JSON、从JSON反序列化到Model

  • 自然地以Model的属性名称作为解析JSON的Key,不需要额外指定

  • 支持Swift中大部分类型

  • 支持class、struct定义的Model

  • 支持自定义解析规则

  • 类型自适应,如JSON中是一个Int,但对应Model是String字段,会自动完成转化

三 安装使用以及封装
3.1 安装

我使用的是cocopod进行包引入管理,修改Prodfie文件,添加如下代码:

pod 'HandyJSON', '~> 4.1.1'

官方说明(swift3.0用大于1.8.0版本的 swift4.0用4.1.1):

To use with Swift 3.x using >= 1.8.0 To use with Swift 4.0 using == 4.1.1 

实际使用过程中,1.8.0是有问题的,有个必现的bug,建议直接用4.1.1版本的,4.1如果安装失败,请更新你的cocopod到最新版

3.2 封装

为了方便我们项目中的使用,我们一般都会在做一层封装,方便库的以后升级替换,以及方便日常业务逻辑的处理

JsonUtil.swift import UIKit import HandyJSON class JsonUtil: NSObject { /** * Json转对象 */ static func jsonToModel(_ jsonStr:String,_ modelType:HandyJSON.Type) ->BaseModel { if jsonStr == "" || jsonStr.count == 0 { #if DEBUG print("jsonoModel:字符串为空") #endif return BaseModel() } return modelType.deserialize(from: jsonStr) as! BaseModel } /** * Json转数组对象 */ static func jsonArrayToModel(_ jsonArrayStr:String, _ modelType:HandyJSON.Type) ->[BaseModel] { if jsonArrayStr == "" || jsonArrayStr.count == 0 { #if DEBUG print("jsonToModelArray:字符串为空") #endif return [] } var modelArray:[BaseModel] = [] let data = jsonArrayStr.data(using: String.Encoding.utf8) let peoplesArray = try! JSONSerialization.jsonObject(with:data!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] for people in peoplesArray! { modelArray.append(dictionaryToModel(people as! [String : Any], modelType)) } return modelArray } /** * 字典转对象 */ static func dictionaryToModel(_ dictionStr:[String:Any],_ modelType:HandyJSON.Type) -> BaseModel { if dictionStr.count == 0 { #if DEBUG print("dictionaryToModel:字符串为空") #endif return BaseModel() } return modelType.deserialize(from: dictionStr) as! BaseModel } /** * 对象转JSON */ static func modelToJson(_ model:BaseModel?) -> String { if model == nil { #if DEBUG print("modelToJson:model为空") #endif return "" } return (model?.toJSONString())! } /** * 对象转字典 */ static func modelToDictionary(_ model:BaseModel?) -> [String:Any] { if model == nil { #if DEBUG print("modelToJson:model为空") #endif return [:] } return (model?.toJSON())! } } 

说明:这里我封装了5个方法,Json转对象,Json转数组对象,字典转对象,对象转JSON,对象转字典,基本覆盖了我们日常开发的常用操作,与这个工具类相对应的,还有一个公共的基础module类

BaseModel.swift import UIKit import HandyJSON class BaseModel: HandyJSON { // var date: Date? // var decimal: NSDecimalNumber? // var url: URL? // var data: Data? // var color: UIColor? required init() {} func mapping(mapper: HelpingMapper) { //自定义解析规则,日期数字颜色,如果要指定解析格式,子类实现重写此方法即可 // mapper <<< // date <-- CustomDateFormatTransform(formatString: "yyyy-MM-dd") // // mapper <<< // decimal <-- NSDecimalNumberTransform() // // mapper <<< // url <-- URLTransform(shouldEncodeURLString: false) // // mapper <<< // data <-- DataTransform() // // mapper <<< // color <-- HexColorTransform() } } 

说明:封装的基础model类。开发中,我们自定义的model继承此model即可省去了每次都要引入 “import HandyJSON” ,以及每次都要实现“required init() {}”方法,子类如果要自定义解析规则,重写mapping方法即可

四 使用示例

基于以上封装的类我们实现几个示例做具体说明,主要有以下几个示例:

  1. Json转模型 (常用)
  2. Json数组转模型 (常用)
  3. 字典转模型
  4. 模型转Json
  5. 模型转字典
  6. json与嵌套的模型相互转换
  7. 特殊类型字段转换,日期类型,数字类型,颜色
1. Json转模型 (常用)
JsonToModel.swift import UIKit class JsonToModel: BaseModel { var id :Int? var color:String? var name:String? } 
 fileprivate func jsonTomodel(){ let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}" let model:JsonToModel = JsonUtil.jsonToModel(jsonString,JsonToModel.self) as! JsonToModel print(model.name as Any) print(model.color as Any) print(model.id as Any) } 

说明:调用jsonToModel(_ jsonStr:String,_ modelType:HandyJSON.Type),传入两个参数,第一个参数是要转换的Json字符串,第二个参数是要转换的model类的class,因为公共类中统一返回的都是BaseModel类型,所以这里要调用 as! 转换成具体的子类类型。

2. Json数组转模型 (常用)
JsonArrayToModel.swift import UIKit class JsonArrayToModel: BaseModel { var name:String? var id :String? } 
//json数组转模型 fileprivate func jsonArrayTomodel() { let jsonArrayString: String = "[{\"name\":\"Bob\",\"id\":\"1\"}, {\"name\":\"Lily\",\"id\":\"2\"}, {\"name\":\"Lucy\",\"id\":\"3\"}]" let cats = JsonUtil.jsonArrayToModel(jsonArrayString, JsonArrayToModel.self) as! [JsonArrayToModel] for model:JsonArrayToModel in cats { print(model.name as Any) } } 

说明:调用jsonArrayToModel(_ jsonArrayStr:String, _ modelType:HandyJSON.Type)传入两个参数,第一个参数是要转换的数组型Json字符串,第二个参数是要转换的model类的class,返回值是一个数组,因为公共类中统一返回的都是BaseModel类型,所以这里要调用 as! 转换成具体的子类类型数组。

3 字典转模型
import UIKit class JsonToModel: BaseModel { var id :Int! var color:String? var name:String? } 
//字典转模型 fileprivate func dicToModel() { var dict = [String: Any]() dict["id"] = 1.1 dict["color"] = "hello" dict["name"] = "李四" let model:JsonToModel = JsonUtil.dictionaryToModel(dict,JsonToModel.self) as! JsonToModel print(model.name as Any) print(model.color as Any) print(model.id as Any) } 

说明:调用dictionaryToModel(_ dictionStr:[String:Any],_ modelType:HandyJSON.Type),传入两个参数,第一个参数是要转换的字典对象,第二个参数是要转换的model类的class,因为公共类中统一返回的都是BaseModel类型,所以这里要调用 as! 转换成具体的子类类型。

4. 模型转Json
JsonToModel.swift import UIKit class JsonToModel: BaseModel { var id :Int! var color:String? var name:String? } 
//模型转json fileprivate func modelToJson() { let model:JsonToModel = JsonToModel() model.color = "red" model.id = 100 model.name = "李四真" let modelTostring = JsonUtil.modelToJson(model) print(modelTostring) } 

说明:调用modelToJson(_ model:BaseModel?),传入一个参数,传入一个module对象,返回值是一个JSON字符串

5. 模型转字典
JsonToModel.swift import UIKit class JsonToModel: BaseModel { var id :Int! var color:String? var name:String? } 
 //模型转字典 fileprivate func modelTodiction() { let model:JsonToModel = JsonToModel() model.color = "red" model.id = 100 model.name = "李四" let modelTostring = JsonUtil.modelToDictionary(model) print(modelTostring["name"] as Any) } 

说明:调用modelToDictionary(_ model:BaseModel?)传入一个参数,传入一个module对象,返回值是一个字典对象,对于日常开发中,有时候后台只返回一个字段,比如返回一个成功信息字段,直接把返回的json串转换成一个字典即可,没必要再构建一个model去转换。

6. json与嵌套的模型相互转换
CombineModel.swift import UIKit class Composition: BaseModel { var aInt:Int? var aString:String? } class CombineModel: BaseModel { var aInt:Int? var comp1:Composition? var comp2:[Composition] = [] } 
//json与嵌套的模型相互转换 fileprivate func jsonTocombilModel() { let model:CombineModel = CombineModel() model.aInt = 1001 let posModel1 = Composition() posModel1.aInt = 1 posModel1.aString = "赵六1" let posModel2 = Composition() posModel2.aInt = 2 posModel2.aString = "赵六2" let posModel3 = Composition() posModel3.aInt = 3 posModel3.aString = "赵六3" model.comp1 = posModel1 model.comp2.append(posModel2) model.comp2.append(posModel3) let modeString = JsonUtil.modelToJson(model) print(modeString) let model2 = JsonUtil.jsonToModel(modeString, CombineModel.self) print(model2) } 

说明:还是调用Json转模型,模型转Json的方法,本例子演示的是对象嵌套,对象里面可以嵌套对象,可以嵌套对象数组,平时我们开发经常遇到这种结构的Json串。

7. 特殊类型字段转换,日期类型,数字类型,颜色
SpacialTypeModel.swift import UIKit import HandyJSON class SpacialTypeModel: BaseModel { var date: Date? var decimal: NSDecimalNumber? var url: URL? var data: Data? var color: UIColor? override func mapping(mapper: HelpingMapper) { mapper <<< date <-- CustomDateFormatTransform(formatString: "yyyy-MM-dd") mapper <<< decimal <-- NSDecimalNumberTransform() mapper <<< url <-- URLTransform(shouldEncodeURLString: false) mapper <<< data <-- DataTransform() mapper <<< color <-- HexColorTransform() } } 
// 特殊类型字段转换,日期类型,数字类型,颜色 fileprivate func jsonToSpecialModel () { let object = SpacialTypeModel() object.date = Date() object.decimal = NSDecimalNumber(string: "1.23423414371298437124391243") object.url = URL(string: "https://www.aliyun.com") object.data = Data(base64Encoded: "aGVsbG8sIHdvcmxkIQ==") object.color = UIColor.blue let specailModelString = JsonUtil.modelToJson(object) print(object.toJSONString()!) // it prints: // {"date":"2017-09-11","decimal":"1.23423414371298437124391243","url":"https:\/\/www.aliyun.com","data":"aGVsbG8sIHdvcmxkIQ==","color":"0000FF"} let mappedObject:SpacialTypeModel = JsonUtil.jsonToModel(specailModelString, SpacialTypeModel.self) as! SpacialTypeModel print(mappedObject.date as Any) } 

说明:本例演示的是对于对象里含有特殊类型字段的转换方法,主要注意点在构建model类里,我们要重写父类mapping方法还要引入HandyJSON头文件

五 总结

objective c有很多成熟的Json与模型转换框架,对于swift就很少有类似的的框架,swift这方面比较成熟的框架swiftJSON,但是这个框架需要写映射关系,感觉还是很不方便的,纵观整个github,这个出自阿里的HandyJSON,不需要写映射关系,方便灵活,功能多样,使用起来还不错。
最后附上:
本文DEMO:https://github.com/lerpo/HandyJSONDemo.git
HandyJSON文档:https://github.com/alibaba/HandyJSON/blob/master/README_cn.md
HandyJSON原理https://yq.aliyun.com/articles/182517?spm=5176.11065265.1996646101.searchclickresult.705a7b7bA0G39T

 

使用HandyJSON导致的内存泄漏问题相关解决方法

https://www.cnblogs.com/zhanbaocheng/p/8125993.html

在移动开发中,与服务器打交道是不可避免的,从服务器拿到的接口数据最终都会被我们解析成模型,现在比较常见的数据传输格式是json格式,对json格式的解析可以使用原生的解析方式,也可以使用第三方的,我们的项目中使用的是阿里开源的一个swift编写的解析框架--HandyJSON。

在使用过程中,使用instruments的Leak Checks工具对内存泄漏进行检测时发现了这个框架导致了不少的内存泄漏,如图1-1:
1-1 修复内存泄漏之前.png

这张图是在APP进入首页并将数据加载完毕时截取的,可以看到,HandyJSON一共导致了28个内存泄漏,如果把整个APP运行一遍,估计内存泄漏的个数会很多很多。

在HandyJSON的GitHub上找到了原因点击前往,是UnsafeMutableRawPointer没有释放导致的,解决方法如下:

在Leak Checks工具中显示了内存泄漏的了类名(Measuable)与方法名(_getBridgedPropertyList),我们找到HandyJSON中的这个类和方法,在里面进行释放UnsafeMutableRawPointer就好了,在方法中加上图1-2中的红框中的代码即可:

1-2 解决方法.png

添加完代码之后,我们重新检测内存泄漏的情况,发现如图1-3所示:

1-3 内存泄漏解决之后.png

同样是进入首页并将数据加载完毕,我们发现,关于HandyJSON导致的内存泄漏已经没有了。

 

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM