|
| 1 | +# #QA2 支持继承关系的解析 |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +## HandyJSON 如何处理继承的解析 |
| 6 | + |
| 7 | +`HandyJSON` 能自动处理继承层级中的所有属性,子类无需额外实现任何方法,使用方式非常简洁,示例如下: |
| 8 | + |
| 9 | +``` |
| 10 | +class BaseModel: HandyJSON { |
| 11 | + var name: String = "" |
| 12 | + required init() { } |
| 13 | +} |
| 14 | +
|
| 15 | +class Model: BaseModel { |
| 16 | + var age: Int = 0 |
| 17 | +} |
| 18 | +
|
| 19 | +let dict = [ |
| 20 | + "name": "小明", |
| 21 | + "age": 10 |
| 22 | +] as [String : Any] |
| 23 | +
|
| 24 | +guard let model = Model.deserialize(from: dict) else { return } |
| 25 | +print(model.age) // 10 |
| 26 | +print(model.name) // 小明 |
| 27 | +``` |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +## SmartCodable 的继承 |
| 34 | + |
| 35 | +``` |
| 36 | +class BaseModel: SmartCodable { |
| 37 | + var name: String = "" |
| 38 | + required init() { } |
| 39 | +} |
| 40 | +
|
| 41 | +@SmartSubclass |
| 42 | +class Model: BaseModel { |
| 43 | + var age: Int = 0 |
| 44 | +} |
| 45 | +
|
| 46 | +let dict = [ |
| 47 | + "name": "小明", |
| 48 | + "age": 10 |
| 49 | +] as [String : Any] |
| 50 | +
|
| 51 | +guard let model = Model.deserialize(from: dict) else { return } |
| 52 | +print(model.age) // 10 |
| 53 | +print(model.name) // 小明 |
| 54 | +``` |
| 55 | + |
| 56 | +> ⚠️ 需要使用5.0+版本。如果使用低版本,需要使用下面提供的方案。 |
| 57 | +
|
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | +## Codable 在4.0+如何处理继承的解析 |
| 65 | + |
| 66 | +Swift 编译器仅会对显式遵循`Codable`协议的**当前类型**自动合成编解码方法。 |
| 67 | + |
| 68 | +当父类遵循 `Codable` 时,其自身的属性会被自动处理。但子类新增属性不会被自动处理,因此我们需要**重写编解码方法,手动实现新增属性的编解码逻辑,并调用super实现**。 |
| 69 | + |
| 70 | +例如:(相比 `HandyJSON`显得繁琐一些😓) |
| 71 | + |
| 72 | +``` |
| 73 | +class BaseModel: Codable { |
| 74 | + var name: String = "" |
| 75 | + required init() { } |
| 76 | +} |
| 77 | +
|
| 78 | +class SubModel: BaseModel { |
| 79 | + var age: Int = 0 |
| 80 | + |
| 81 | + private enum CodingKeys: CodingKey { |
| 82 | + case age |
| 83 | + } |
| 84 | + |
| 85 | + required init(from decoder: Decoder) throws { |
| 86 | + let container = try decoder.container(keyedBy: CodingKeys.self) |
| 87 | + self.age = try container.decode(Int.self, forKey: .age) |
| 88 | + try super.init(from: decoder) |
| 89 | + } |
| 90 | + |
| 91 | + override func encode(to encoder: Encoder) throws { |
| 92 | + try super.encode(to: encoder) |
| 93 | + var container = encoder.container(keyedBy: CodingKeys.self) |
| 94 | + try container.encode(age, forKey: .age) |
| 95 | + } |
| 96 | + |
| 97 | + required init() { super.init() } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | + |
| 102 | +### 为什么子类必须手动实现? |
| 103 | + |
| 104 | +我们可以通过`SIL(Swift Intermediate Language)`验证编译器的行为。 |
| 105 | +``` |
| 106 | +class BaseModel : Decodable & Encodable { |
| 107 | +
|
| 108 | + @_hasStorage @_hasInitialValue var name: String { get set } |
| 109 | + required init() |
| 110 | + |
| 111 | + enum CodingKeys : CodingKey { |
| 112 | + case name |
| 113 | + |
| 114 | + func hash(into hasher: inout Hasher) |
| 115 | + init?(stringValue: String) |
| 116 | + init?(intValue: Int) |
| 117 | + var hashValue: Int { get } |
| 118 | + var intValue: Int? { get } |
| 119 | + var stringValue: String { get } |
| 120 | + } |
| 121 | + |
| 122 | + @objc deinit |
| 123 | + func encode(to encoder: Encoder) throws |
| 124 | + required init(from decoder: Decoder) throws |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +``` |
| 129 | +@_inheritsConvenienceInitializers class SubModel : BaseModel { |
| 130 | + @_hasStorage @_hasInitialValue var age: Int { get set } |
| 131 | + required init() |
| 132 | + required init(from decoder: Decoder) throws |
| 133 | + @objc deinit |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +可以看到: |
| 138 | + |
| 139 | +- 对于父类,由于显式遵循了 `Codable` 协议,编译器自动合成了`init(from decoder:)`、`encode(to encoder:)`和`CodingKeys` |
| 140 | + |
| 141 | +- 对于子类: |
| 142 | + |
| 143 | + - **不会自动合成** `encode(to:)`(不是 `required` 方法, 子类也没有显式的遵循`Codable`协议) |
| 144 | + |
| 145 | + |
| 146 | + - **不会自动合成** `CodingKeys`(没有显式的遵循`Codable`协议) |
| 147 | + |
| 148 | + |
| 149 | + - 会合成 `init(from:)`(因为是`required`初始化方法,合成的这个方法中**不会包含子类新增属性的解码逻辑**) |
| 150 | + |
| 151 | + |
| 152 | +因此,若子类也有需要被编码/解码的属性, 就**必须在子类中重写** `init(from:)` 和 `encode(to:)`。 |
| 153 | + |
| 154 | + |
| 155 | + |
| 156 | +## SmartCodable 如何处理继承的解析 |
| 157 | + |
| 158 | +`SmartCodable` 是对原生 `Codable` 的增强,天然支持`Codable`继承的处理方案,也提供了其它方案选择,可以根据各自项目的情况选择最优方案。 |
| 159 | + |
| 160 | +- 基于继承的实现(类似原生Codable) |
| 161 | +- 基于Protocol的实现 |
| 162 | +- 基于@SmartFlat的实现 |
| 163 | +- 基于Protocol + @SmartFlat的混合实现 |
| 164 | + |
| 165 | + |
| 166 | +### 方案一:基于继承的实现(同Codable) |
| 167 | +与原生 Codable 实现类似,但使用 `SmartCodable` 增强解析器,具备类型容错能力。 |
| 168 | + |
| 169 | +**优点:** |
| 170 | + |
| 171 | +- 原生 `Codable` 写法,符合直觉 |
| 172 | +- 支持类型不一致、字段缺失、nil等场景的容错 |
| 173 | + |
| 174 | +**缺点:** |
| 175 | + |
| 176 | +- 子类仍需手动实现新增属性的编解码逻辑,代码量较多 |
| 177 | + |
| 178 | +``` |
| 179 | +class BaseModel: SmartCodable { |
| 180 | + var name: String = "" |
| 181 | + required init() { } |
| 182 | +} |
| 183 | +
|
| 184 | +class SubModel: BaseModel { |
| 185 | + var age: Int = 0 |
| 186 | + |
| 187 | + private enum CodingKeys: CodingKey { |
| 188 | + case age |
| 189 | + } |
| 190 | + |
| 191 | + required init(from decoder: Decoder) throws { |
| 192 | + let container = try decoder.container(keyedBy: CodingKeys.self) |
| 193 | + self.age = try container.decode(Int.self, forKey: .age) |
| 194 | + try super.init(from: decoder) |
| 195 | + } |
| 196 | + |
| 197 | + override func encode(to encoder: Encoder) throws { |
| 198 | + try super.encode(to: encoder) |
| 199 | + var container = encoder.container(keyedBy: CodingKeys.self) |
| 200 | + try container.encode(age, forKey: .age) |
| 201 | + } |
| 202 | + |
| 203 | + required init() { super.init() } |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +### 方案二:基于Protocol的实现 |
| 208 | +通过协议定义公共属性,避免继承带来的复杂度。适用轻量级共享属性定义。 |
| 209 | +**优点:** |
| 210 | + |
| 211 | +- 不需要手动实现子类编解码逻辑 |
| 212 | +- 子类间具有协议这个公共类型 |
| 213 | + |
| 214 | +**缺点:** |
| 215 | + |
| 216 | +- 每个子类都需实现协议中的属性,存在一定重复 |
| 217 | +``` |
| 218 | +protocol BaseModel { |
| 219 | + var name: String { set get } |
| 220 | + var sex: Int { set get } |
| 221 | +} |
| 222 | +
|
| 223 | +class SubModel: BaseModel, SmartCodable { |
| 224 | + required init() {} |
| 225 | + |
| 226 | + var name: String = "" |
| 227 | + var sex: Int = 0 |
| 228 | + |
| 229 | + var age: Int = 0 |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +### 方案三:基于@SmartFlat的实现 |
| 234 | +使用组合代替继承,将父类作为属性嵌入到子类中。`@SmartFlat`属性包装器会从当前JSON节点提取数据填充该属性。 |
| 235 | +**优点:** |
| 236 | + |
| 237 | +- 不需要手动实现子类编解码逻辑 |
| 238 | +- 避免了`Protocol`方案中,各子类重复实现基协议的繁琐 |
| 239 | + |
| 240 | +**缺点:** |
| 241 | + |
| 242 | +- 缺失子类间的公共类型 |
| 243 | +``` |
| 244 | +class BaseModel: SmartCodable { |
| 245 | + required init() {} |
| 246 | + |
| 247 | + var name: String = "" |
| 248 | + var sex: Int = 0 |
| 249 | +} |
| 250 | +
|
| 251 | +class SubModel: SmartCodable { |
| 252 | + required init() {} |
| 253 | + |
| 254 | + var age: Int = 0 |
| 255 | + |
| 256 | + @SmartFlat |
| 257 | + var manBase: BaseModel = .init() |
| 258 | +} |
| 259 | +
|
| 260 | +let dict = [ |
| 261 | + "name": "小明", |
| 262 | + "sex": 1, |
| 263 | + "age": 10, |
| 264 | +] as [String : Any] |
| 265 | +
|
| 266 | +guard let model = SubModel.deserialize(from: dict) else { return } |
| 267 | +print(model.manBase.name) // 小明 |
| 268 | +print(model.manBase.sex) // 1 |
| 269 | +print(model.age) // 10 |
| 270 | +``` |
| 271 | + |
| 272 | +### 方案四:基于Protocol + @SmartFlat的实现 |
| 273 | +结合`Protocol` 和`@SmartFlat`两种方案的优点,规避各自的不足,比较灵活。 |
| 274 | +**优点:** |
| 275 | + |
| 276 | +- 不需要手动实现子类编解码逻辑 |
| 277 | +- 避免了`Protocol`方案中,各子类重复实现基协议的繁琐 |
| 278 | +- 避免了`@SmartFlat`方案中,缺失了各子类的公共类型约束 |
| 279 | + |
| 280 | +**缺点:** |
| 281 | + |
| 282 | +- 不是真正的继承😂 |
| 283 | + |
| 284 | +``` |
| 285 | +protocol ManBaseModelProtocol { |
| 286 | + var manBase: BaseModel { set get } |
| 287 | +} |
| 288 | +
|
| 289 | +class BaseModel: SmartCodable { |
| 290 | + required init() {} |
| 291 | + |
| 292 | + var name: String = "" |
| 293 | + var sex: Int = 0 |
| 294 | +} |
| 295 | +
|
| 296 | +class SubModel: SmartCodable, ManBaseModelProtocol { |
| 297 | + required init() {} |
| 298 | + |
| 299 | + @SmartFlat |
| 300 | + var manBase: BaseModel = .init() |
| 301 | + |
| 302 | + var age: Int = 0 |
| 303 | +} |
| 304 | +``` |
0 commit comments