姬長信(Redy)

Swift学习之@dynamicMemberLookup与@dynamicCallable

## @dynamicMemberLookup #### 介绍 Swift 4.2 中引入了一个新的语法`@dynamicMemberLookup`/uff08动态成员查找/uff09。使用`@dynamicMemberLookup`标记了目标/uff08类、结构体、枚举、协议/uff09/uff0c实现`subscript(dynamicMember member: String) `方法后我们就可以访问到对象不存在的属性。 #### 核心内容 - `@dynamicMemberLookup`/uff1a标记类、结构体、枚举、协议 - `subscript(dynamicMember member: String) `/uff1a实现该方法/uff0c可以像数组和字典一样/uff0c用下标的方式去访问属性/uff0c通过所请求属性的字符串名得到并返回想要的值 #### 基本使用 - 错误的代码 ```swift struct Person { } let p = Person() // 结构体没有定义name属性/uff0c所以会报错 // Value of type 'Person' has no member 'name' print(p.name) ``` - 有了动态成员查找 ```swift // 标记 @dynamicMemberLookup struct Person { // 实现方法 subscript(dynamicMember member: String) -> String { let properties = ["name":"Zhangsan", "age": "20", "sex": "男"] return properties[member, default: "unknown property"] } } let p = Person() print(p.name) // 打印 Zhangsan print(p.age) // 打印 20 print(p.sex) // 打印 男 ``` **解读** - 声明了`@dynamicMemberLookup`后/uff0c即使属性没有定义/uff0c但是程序会在运行时动态的查找属性的值/uff0c调用`subscript(dynamicMember member: String)`方法来获取值。 - `subscript(dynamicMember member: String)`方法的返回值类型根据访问属性的类型决定。 - 由于安全性的考虑/uff0c如果实现了这个特性/uff0c返回值不能是可选值/uff0c一定要有值返回。 #### 多类型查找 既然是动态查找/uff0c如果两个属性类型不同/uff0c怎么办/uff1f解决办法是重载`subscript(dynamicMember member: String) `方法。和泛型的逻辑类似/uff0c通过类型推断来选择对应的方法。`但是此时调用的时候/uff0c所有属性必须显示声明类型/uff0c否则会报错`。 ```swift @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) // 打印 男 ``` ## @dynamicCallable #### 介绍 Swift 5 中引入了一个新的语法`@dynamicCallable`/uff08动态可调用/uff09。使用`@dynamicCallable`标记了目标以后/uff08类、结构体、枚举、协议/uff09/uff0c实现`dynamicallyCall `方法后/uff0c目标可以像调用函数一样使用。 #### 核心内容 - `@dynamicCallable`/uff1a标记类、结构体、枚举、协议 - `dynamicallyCall `/uff1a实现该方法/uff0c可以像调用函数一样去调用类型/uff0c需要指定接收的参数和参数类型。 #### 基本使用 ```swift // 标记 @dynamicCallable struct Person { // 实现方法一 func dynamicallyCall(withArguments: [String]) { for item in withArguments { print(item) } } // 实现方法二 func dynamicallyCall(withKeywordArguments: KeyValuePairs){ for (key, value) in withKeywordArguments { print("/(key) --- /(value)") } } } let p = Person() p("zhangsan") // 等于 p.dynamicallyCall(withArguments: ["zhangsan"]) p("zhangsan", "20", "男") // 等于 p.dynamicallyCall(withArguments: ["zhangsan", "20", "男"]) p(name: "zhangsan") // 等于 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan"]) p(name: "zhangsan", age:"20", sex: "男") // 等于 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan", "age": "20", "sex": "男"]) ``` **解读** - 声明了`@dynamicMemberLookup`后/uff0c必须实现`dynamicallyCall(withArguments:) `和` dynamicallyCall(withKeywordArguments:)`两个方法中的至少一个/uff0c否则编译器会报错。 - 当目标调用的时候/uff0c会转换成方法的调用/uff0c然后传入对应的参数与参数类型。 - 实现了`dynamicallyCall(withArguments:) ` - 参数类型根据自己需要调整/uff0c如上例`[String]`。 - 当目标调用的时候/uff0c参数不带标签。 - 参数为数组时/uff0c可以理解为可变参数/uff0c调用时传入的参数可以是1个/uff0c也可以是多个。 - 实现了` dynamicallyCall(withKeywordArguments:)` - 参数类型为`KeyValuePairs`/uff0c暂时可以把它当成字典来用/uff0c主要改变的是`value`的类型/uff0c如上例中为`String`。 - 当目标调用的时候/uff0c参数带标签。 #### 注意事项 - 如果实现` dynamicallyCall(withKeywordArguments:)`但没有实现`dynamicallyCall(withArguments:) `/uff0c也可以在没有参数标签的情况下调用 - 如果实现` dynamicallyCall(withKeywordArguments:)`或`dynamicallyCall(withArguments:) `时标记为`throw`/uff0c则调用该类型也将被抛出`throw` - 扩展无法添加`@dynamicCallable`/uff0c只能添加到主要类型上 #### KeyValuePairs 在 Swift 5 中/uff0c之前的`DictionaryLiteral`类型被重命为`KeyValuePairs`。 - 字典有一个构造函数`public init(dictionaryLiteral elements: (Key, Value)...)`/uff0c其中后面的那一串就是`DictionaryLiteral`/uff0c即`KeyValuePairs`。 ```swift let dic = Dictionary(dictionaryLiteral: ("name","zhangsan"), ("age","20"),("sex","男")) print(dic["name"]) ``` - `KeyValuePairs`只有一个构造函数 ```swift // 构造函数 public init(dictionaryLiteral elements: (Key, Value)...) // 构造一个person let person: KeyValuePairs = KeyValuePairs(dictionaryLiteral: ("name","zhangsan"), ("age","20"),("sex","男")) ``` - 与字典的区别 官方介绍/uff1a**字典中键值对的顺序是不可预测的。 如果您需要有序的键值对集合并且不需要`Dictionary`提供的快速键查找/uff0c请使用`KeyValuePairs`类型以获取替代方案。** #### 意义 Swift 目前可以与 C、OC 交互。但如 Python 、 JavaScript 等则不行/uff0c如果 Swift 能够调用 Python 、JavaScript 等语言/uff0c那么毫无疑问会极大的拓展的 Swift 的使用范围。 想要实现这一点/uff0c`@dynamicMemberLookup`、 `@dynamicCallable`双剑合璧或可实现。首先通过`@dynamicMemberLookup`动态的返回一个函数/uff0c再通过`@dynamicCallable`来调用。