看Alamofire源码的时候,看到@propertyWrapper和@dynamicMemberLookup,这篇说下dynamicMemberLookup
Swift 4.2 中引入了一个新的语法@dynamicMemberLookup
(动态成员查找)。使用@dynamicMemberLookup
标记了目标(类、结构体、枚举、协议),实现subscript(dynamicMember member: String)
方法后我们就可以访问到对象不存在的属性。
@dynamicMemberLookup
:可标记类、结构体、枚举、协议subscript(dynamicMember member: String)
:实现该方法,可以像数组和字典一样,用下标的方式去访问属性,通过所请求属性的字符串名得到并返回想要的值- 可提高与 Python 或 Javascript 等动态语言的互操作性。它允许动态成员查找调用看起来像访问类型属性的常规调用:
let people = People()
let name = people.name // 像访问属性一样
name是从字典中查找的,而不是作为 People 的属性访问的。
如何使用动态成员查找?
要在你的自定义类型中使用动态成员查找,请使用 @dynamicMemberLookup标注你的类型,并实现如下方法:
subscript(dynamicMember:)
该方法接收一个String或KeyPath类型参数
我们来看看People是如何实现动态成员查找的,下面是一个demo:
@dynamicMemberLookup
struct People {
private var map = ["name": "drbox", "job": "developer"]
subscript(dynamicMember key: String) -> String {
map[key] ?? "undefine"
}
}
现在你可以像访问它的属性一样查找 People 对象的成员。
let people = People()
let name = people.name
let job = people.job
我们分析一下People是如何实现动态成员查找的
- 首先我们使用@dynamicMemberLookup标注了People类型
- 实现了subscript(dynamicMember:)方法,该方法我们接收一个String类型的参数,并返回一个String类型值
- 声明了
@dynamicMemberLookup
后,即使属性没有定义,但是程序会在运行时动态的查找属性的值,调用subscript(dynamicMember member: String)
方法来获取值。 -
如果类、结构体有对应的属性, 会优先查找属性, 属性查找不到 才会到dynamicMemberLookup 流程
subscript(dynamicMember member: String)
方法的返回值类型根据访问属性的类型决定。- 由于安全性的考虑,如果实现了这个特性,返回值不能是可选值,一定要有值返回。
通过KeyPath来看看另一个有用的例子。
struct Info {
let job: String
let homeAddr: String
}
struct People {
let name: String
let info: Info
}
let info = Info(job: "developer", homeAddr: "beijing")
let people = People(name: "drbox", info: info)
print("job: \(people.info.job)") // 访问成员属性的属性
通过调用people.info.job可以访问Info的属性值。但是,如果你想直接通过people.job 而不是people.info.job 调用它怎么办?
这时动态成员查找的KeyPath就派上用场了。
@dynamicMemberLookup
struct People {
let name: String
let info: Info
subscript<T>(dynamicMember path: KeyPath<Info, T>) -> T {
info[keyPath: path]
}
}
let info = Info(job: "developer", homeAddr: "beijing")
let people = People(name: "drbox", info: info)
print("job: \(people.job)") // 直接访问成员属性的属性
返回值多类型查找
既然是动态查找,如果两个属性类型不同,怎么办?解决办法是重载subscript(dynamicMember member: String)
方法。和泛型的逻辑类似,通过类型推断来选择对应的方法。但是此时调用的时候,所有属性必须显示声明类型,否则会报错
。
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> String {
let properties = ["name":"Zhangsan", "sex": "男"]
return properties[member, default: "unknown property"]
}
subscript(dynamicMember member: String) -> Int {
let properties = ["age": 20]
return properties[member, default: 0]
}
}
let p = Person()
// 类型必须明确
let name: String = p.name
print(name) // 打印 Zhangsan
let age: Int = p.age
print(age) // 打印 20
let sex: String = p.sex
print(sex) // 打印 男
它让people.job成为了可能。这只是一个用于讲解动态成员查找的简单案例,但在实际开发中,在特定场合可以发挥作用,正常的场景我们最好不要这么做。