iOS 开发中经常遇到的数据类型转换发生在网络请求回来的时候,字符串转成 Foudation Object(比如 Dictionary 或者 Array)然后再转换成我们定义好的 Model 类型,一般来说就是 JSON 转 Model。
# Objective-C 时代的解决方案
之前使用 Objective-C 进行开发的时候,OC 并没有提供对 JSON 转化成 Model 很好的支持。我们手动进行转换的时候通常要自己手写一些转换的方法,比如
@interface Student : NSObject
@property(copy) NSString *name;
+ (Student *)fromDict:(NSDictionary *)dict;
@end
@implementation Student
+ (Student *)fromDict:(NSDictionary *)dict {
Student *student = [Student new];
student.name = dict[@"name"];
return student;
}
@end
//完整的转化过程 String -> Foundation Object -> Model
NSString *string = @"{\"name\":\"fanthus\"}";
NSData *jsonData = [string dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
Student *stu = [Student fromDict:dict];
NSLog(@"name %@",stu.name);
上面的代码里我们是通过 NSJSONSerialization
类将 JSON Data 转化为 Foundation Object的,将 Foundation Object 转化为 JSON Data 的时候也是通过 NSJSONSerialization
这个类,只不过 API 变成了 + (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
。NSJSONSerialization
本身并没有直接参与到 Model 和 Foundation 的转换过程中。
但是这样进行 Foundation Object 和 Model 的转换就非常的麻烦,每个 Model 都需要实现一遍转换逻辑,非常的麻烦,于是涌现出了很多解决这种问题的库,比如 MJExtension (opens new window),YYModel (opens new window) 等这样库。
# Swift 时代的解决方案
Swift 引入了 Codable 协议,目的就是简化 JSON 和 Model 转换的难度。比如下面这种
struct Student: Codable {
let name:String
}
//JSON->Model
let json = "{\"name\":\"fanthus\"}"
let student = try JSONDecoder().decode(Student.self, from: json.data(using: .utf8)!)
print(student)
//Model->JSON
let data = try JSONEncoder().encode(student)
let object = try JSONSerialization.jsonObject(with: data)
print(object)
原生的 JSONEncoder
就能直接很方便的进行转化了。我们只需要让我们的 Model 类型遵守 Encodable
和 Decodable
协议。只要 Model 类型的所有属性是 Codable
的,那我们类型就不用做额外的工作,比如 Student
的 name 属性类型是 String
,String
类型是 Codable
的,所以 Student
就直接声明遵守 Codable
协议就好了。当然我们自定义类型的属性,只要自定义类型符合 Codable
协议,那类型也是自动遵守 Codable
协议的。
Codable
协议是Encodable
和Decodable
的组合public typealias Codable = Decodable & Encodable
Codable
还引入了 CodingKeys
枚举来实现对某些属性编码忽略以及对编解码用到的别名的支持。CodingKeys 本质上就是一个属性验证列表,包含在内的属性就会被编解码,反之不会。比如下面这种场景:
struct Student: Codable {
var name:String
var score:Int = 0 //score 被 CodingKeys 忽略之后需要提供初始值.
var classroom:Int
enum CodingKeys: String, CodingKey {
case name = "name"
case classroom = "class_room"
}
}
let json = "{\"name\":\"fanthus\",\"class_room\":1}"
let student = try JSONDecoder().decode(Student.self, from: json.data(using: .utf8)!)
print(student)
//打印结果: Student(name: "fanthus", score: 0, classroom: 1)
总的来说,原生的 Codable
就能解决我们日常 JSON 转 Model 的场景。
但是有的场景用起来还是不舒服,比如上面 json 字符串里面如果少了 。所以出现很多新的库来满足更加定制化的转化需求,比如 HandyJSON (opens new window)、KakaJSON (opens new window)。class_room
字段的话,解析就会发生异常了,就算是给 classroom
属性提供默认值也有问题(直接将 classroom 属性设置为 optional 就好了)
我有的项目用的是 HandyJSON,但是 HandyJSON 用到了非常底层的 Swift 语言机制,导致如果系统升级底层有变动的话,就会导致序列化失败,程序崩溃,至今 HandyJSON 的 issue 区还有类似的问题 (opens new window)。
综上,想要有完美的解决方案是不行的,感觉 Swift 还是需要再完善一下对 Codable
的支持,让它能兼容更多的场景这样就更好了。
参考地址: