在我们的工作中,各种特殊情况都有可能遇到,某些特定情况下,需要我们记录模型的解析路径,例如:
{
"owner":{
"name":"zhy",
"age":18
},
"visitor":{
"name":"weixian",
"age":18
}
}
对应的模型如下:
struct Role: Codable {
var owner: User
var visitor: User
}
struct User: Codable {
var name: String
var age: Int
}
1、我们想要知道 Role
的属性的名字的字符串,这个时候利用 Codable
中的Decoder
协议提供的 API 来获取解析路径:
/// A type that can decode values from a native format into in-memory
/// representations.
public protocol Decoder {
/// The path of coding keys taken to get to this point in decoding.
var codingPath: [CodingKey] { get }
...
}
codingPath
的注释为: 在解码中达到这一点所采用的编码 Key 路径。
所以我们重写User
的解析过程,并新增属性存储解析路径:
struct User: Codable {
var name: String
var age: Int
var path: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.path = decoder.codingPath.last?.stringValue ?? ""
}
}
此时,我们拿到解析结果 owner.path
即为 owner
2. 我们想要知道 name
的属性名字的字符串,同时获取对应角色名 即想知道其是 owner.name
还是 visitor.name
我们仍准备采用上面的方法,但是我没找到直接重写 Int
, String
解析过程的方法,就算是找到了,好像也太麻烦了,同时还需要手动添加 path
属性, 有简单的方法吗?
有,利用属性包装器 可以帮我们简化调用,利用 OC runtime 的属性关联值以及Swift 的协议默认实现可以帮我们新增属性:
我们扩展DeCodable
协议,要求所有遵守此协议的对象持有 codablePath
属性和 codablePathName
属性,并且提供默认实现:
struct ZYAssociateKeys {
static var ZYCodablePath: String = "ZYCodablePath"
static var ZYCodablePathName: String = "ZYCodablePathName"
}
public extension Decodable {
var codablePath: String {
get { return objc_getAssociatedObject(self, &ZYAssociateKeys.ZYCodablePath) as? String ?? ""}
set { objc_setAssociatedObject(self, &ZYAssociateKeys.ZYCodablePath, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
}
var codablePathName: String {
get { return objc_getAssociatedObject(self, &ZYAssociateKeys.ZYCodablePathName) as? String ?? ""}
set { objc_setAssociatedObject(self, &ZYAssociateKeys.ZYCodablePathName, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
}
}
此时不管是 Role
还是 User
亦或者是 String
, Int
, Double
之类都将自动获得这两个属性
下面我们实现一个 Path
属性包装器:
@propertyWrapper struct Path: Codable {
var wrappedValue: Value
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
decoder.codingPath.forEach {
if !wrappedValue.codablePath.isEmpty {
wrappedValue.codablePath.append(".")
}
wrappedValue.codablePath.append($0.stringValue)
}
wrappedValue.codablePathName = decoder.codingPath.last?.stringValue ?? ""
}
}
typealias path = Path
此时我们的模型写成:
struct Role: Codable {
@path var owner: User
@path var visitor: User
}
struct User: Codable {
@path var name: String
@path var age: Int
}
我们获取一下结果看是否符合预期:
let model = try? JSONDecoder().decode(Role.self, from: json.data(using: .utf8)!)
let oP = model?.owner.codablePath
let opN = model?.owner.codablePathName
let namePath = model?.owner.name.codablePath
let namePahtName = model?.owner.name.codablePathName
print(oP)
print(opN)
print(namePath)
print(namePahtName)
/* ---- log ---- */
Optional("")
Optional("")
Optional("owner.name")
Optional("name")
很遗憾,并没有达到我们的预期,这是因为swift 中 struct
是值类型,无法利用关联值给其新增属性,所以只能改成:
class User: Codable {
@path var name: String
@path var age: Int
}
这个是时候变一切都按预期运行了~