Swift 中的 @dynamicMemberLookup注解

什么是 @dynamicMemberLookup

字面意思来理解就是动态成员查找, 简单理解其目的就是动态来获取属性的值。

// 注解要使用的类或者结构体支持动态成员查找
@dynamicMemberLookup
struct Worker {
    // 必须要实现的方法。返回值类型可自定义
        subscript(dynamicMember member: String) -> String {
            let properties = ["name": "LiaoWorking", "city": "Shanghai"]
            return properties[member, default: ""]
        }
}

let worker = Worker()
let name = worker.name    // liaoworking

看到这里你肯定会一脸黑人问号❓❓❓
Worker没有name这个属性 我为什么会取到name的值。
难道编译器就不检查一下么。
这里编译器的确就管都不管一下了。 dynamiclookup这两个词的意思马上就体现出来了。动态去查找

Demo中当执行下面这行代码时

let name = worker.name 

实际上调用了我们实现的subscript方法,把name当”name”参数传递进去。 所以就得到了 name 为 liaoworking的结果。

下面再来看看关于@dynamicMemberLookUp更多的使用方法。

  1. 多类型的返回
    // 注解说明要使用的类或者结构体支持动态成员查找    
    @dynamicMemberLookup
    struct Worker {
        // 返回值为String
            subscript(dynamicMember member: String) -> String {
                let properties = ["age": "17"]
                return properties[member, default: ""]
            }
            // 返回值为Int
             subscript(dynamicMember member: String) -> Int {
                let properties = ["age": 18]
                return properties[member, default: -1]
            }

    }

    let worker = Worker()
    // 这里系统可根据类型判断 来调用不同的subscript实现
    let ageString: String = worker.age    // “17”
    let ageInt: Int = worker.age //19

上面的Demo具体使用场景还不太明确,只是有这样的用法。这里提一下。

  1. 下面这个Demo就很能说明@dynamicMemberLookup 的一个有用场景。
    我们来创建一个JSON解析Struct

    @dynamicMemberLookup
    enum JSON {
       case intValue(Int)
       case stringValue(String)
       case arrayValue(Array<JSON>)
       case dictionaryValue(Dictionary<String, JSON>)

       var stringValue: String? {
          if case .stringValue(let str) = self {
             return str
          }
          return nil
       }

       subscript(index: Int) -> JSON? {
          if case .arrayValue(let arr) = self {
             return index < arr.count ? arr[index] : nil
          }
          return nil
       }

       subscript(key: String) -> JSON? {
          if case .dictionaryValue(let dict) = self {
             return dict[key]
          }
          return nil
       }

       subscript(dynamicMember member: String) -> JSON? {
          if case .dictionaryValue(let dict) = self {
             return dict[member]
          }
          return nil
       }
    }

如果没有@dynamicMemberLookup关键字。我们操作一个JSON实例就会像下面这样:

json[0]?["name"]?["first"]?.stringValue

当使用@dynamicMemberLookup关键字后

json[0]?.name?.first?.stringValue

这样的语法糖可以让你的代码语义话更强。阅读起来更方便。

你可能会想为什么会有 @dynamicMemberLookUp 这种迷幻的存在,在第一个Demo中直接定义一个属性name不就好了么。
其实@dynamicMemberLookUp的设计不单单是为了纯Swift的工程而考虑,像在机器学习,或者与其他语言之间相互桥接会有很大的用处。 这对于Swift语言来说还是很有帮助的。

参考资料:
https://www.hackingwithswift.com/articles/55/how-to-use-dynamic-member-lookup-in-swift

https://www.swiftbysundell.com/tips/combining-dynamic-member-lookup-with-key-paths