swift 4.1 遇到的问题
苹果前几天发布了 swift 4.1,相对于 swift 4.0 并没有太大变动。但有的三方库确出了问题,比如用于 JSON 解析的 HandyJSON。作者的解释是:
swift4.1调整了类描述结构的内存布局,有个字段的含义改了,偏移量需要重新计算。
看了下 fix 后的源码,之后几行改动
#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
return NominalTypeDescriptor(pointer: relativePointer(base: base, offset: base.pointee - base.hashValue))
#else
return NominalTypeDescriptor(pointer: relativePointer(base: base, offset: base.pointee))
#endif
我的项目中并没有用到 HandyJSON,而是 用的了 swift4.0 中推出的 Codable 来解析 JSON。由于 swift4.1 相对于 swift4.0 的改动不大,理论上应该可以无痛过渡到 4.1,但不幸的是依然出了问题。出问题的地方恰恰是 JSON 解析这一块😂。说好的无痛呢😭😭
由于某些原因,项目中的部分 model 用的是 class,其他的则是用的 struct. 看了下 xcode 的报错信息,错误都出现在类型为 class 的 model 类中。而错误信息则是:
Class 'xxx' has no initializers

我们知道遵循了 codable 协议的 class 或者 struct, 编译器会自动添加相应的协议实现,即自动添加 public init(from decoder: Decoder) throws 和 public func encode(to encoder: Encoder) throws 的实现。
已经有了一个 init(from decoder: Decoder) 的 initializer了, 为什么还报出Class 'xxx' has no initializers 的错误呢?百思不得解😣😣
为了排除其他不相干的因素的影响,单独创建一个空白项目,创建一个类型为 class 的 model.
class AdModel: Codable {
var id: Int
var link: String?
var pic: String?
}
和上图中的代码一毛一样,编译运行,Build Succeeded。没有报错😒,为什么?接着往下。
在 ViewController 的 viewDidLoad() 中 添加如下代码:
let jsonString =
"""
{
"id": 1,
"link": "http://www.google.com",
"pic": null
}
"""
let data = jsonString.data(using: .utf8)!
do {
let admodel = try JSONDecoder().decode(AdModel.self, from: data)
print(admodel)
} catch {
print(error)
}
编译运行,Build Failed。报错😒😒,Class 'AdModel' has no initializers。
如果说手动添加两个协议方法的实现呢? 将下面的代码放到 AdModel 中:
private enum CodingKeys: String, CodingKey {
case id
case link
case pic
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
link = try? container.decode(String.self, forKey: .link)
pic = try? container.decode(String.self, forKey: .pic)
}
func encode(to encoder: Encoder) throws {
var containder = encoder.container(keyedBy: CodingKeys.self)
try containder.encode(id, forKey: .id)
try containder.encode(link, forKey: .link)
try containder.encode(pic, forKey: .pic)
}
编译运行,Build Succeeded, 控制台输出解析后的 model。
到这里基本可以确定是因为什么了,猜测是因为编译器没有给 class 添加 codable 协议方法的默认实现。为了确定猜想是否正确,再 Google 一番,StackOverflow的一篇帖子情况与我的案例有点相似。在评论区找到一条评论说是 swift 的一个 bug。既然是个 bug,不可能等到苹果爸爸来修复。
最简单同时最高效的解决办法就是将类型为 class 改为 struct, 由于将引用类型改为了值类型,所以代码中用到了为 class 类型的 model 的代码需要进行相应的修改。幸运的是类型是 class 的 model 一共就两个,改起来很简单,倘若多的话就悲剧了。
本想藉此机会再写一下关于 Codable 的一些东西,写来写去都是那些东西,网上重复的一大篇就不再重复了,贴几个写的比较好的帖子: