细说 Swift 4.2 新特性:Dynamic Member Lookup

作者 : 开心源码 本文共4162个字,预计阅读时间需要11分钟 发布时间: 2022-05-11 共82人阅读

Swift 4.2 的新特性这两篇文章已经详情的很清楚了:WWDC 2018:Swift 升级了什么,Swift 4.2 新特性升级。但是 4.2 中实现的 dynamic member lookup 苹果在 WWDC 上却完全没有提到。然而我认为这是一个对未来有着重要影响的特性,所以这里单独详情一下。

语法

这个特性中文能叫动态查找成员。在用@dynamicMemberLookup标记了对象后(对象、结构体、枚举、protocol),实现了subscript(dynamicMember member: String)方法后我们即可以访问到对象不存在的属性。假如访问到的属性不存在,就会调使用到实现的 subscript(dynamicMember member: String)方法,key 作为 member 传入这个方法。
比方我们公告了一个结构体,没有公告属性。

@dynamicMemberLookupstruct Person {    subscript(dynamicMember member: String) -> String {        let properties = ["nickname": "Zhuo", "city": "Hangzhou"]        return properties[member, default: "undefined"]    }}//执行以下代码let p = Person()print(p.city)print(p.nickname)print(p.name)

假如没有公告@dynamicMemberLookup的话,执行的代码一定会编译失败。很显然作为一门类型安全语言,编译器会告诉你不存在这些属性。但是在公告了@dynamicMemberLookup后,尽管没有定义 city等属性,但是程序会在运行时动态的查找属性的值,调使用subscript(dynamicMember member: String)方法来获取值。

这样安全吗?

Swift 面世时就大谈自己的安全特性,现在来了这么一个无限制访问的成员万一返回的是nil不就闪退了?是的,出于安全的起因,假如实现了这个特性,你就不可以返回可选值。必需解决好心料外的情况,肯定要有值返回。不像常规的subscript方法能返回可空的值。

说好的动态查找,假如两个属性类型不一样怎样破

这个方法能被重载。和泛型的逻辑相似,会根据你要的返回值而通过类型推断来选择对应的subscript方法。

@dynamicMemberLookupstruct Person {    subscript(dynamicMember member: String) -> String {        let properties = ["nickname": "Zhuo", "city": "Hangzhou"]        return properties[member, default: "undefined"]    }    subscript(dynamicMember member: String) -> Int {        return 18    }}

但是执行的时候就肯定要告诉编译器你要获取的属性是什么类型的,否则会编译错误。

let p = Person()let age: Int = p.ageprint(age)  // 18

Swift 中函数是一等公民,所以返回函数也是能的。

@dynamicMemberLookupstruct Person {   subscript(dynamicMember member: String) -> (_ input: String) -> Void {        return {            print("Hello! I live at the address \($0).")        }    }}

居然能继承!

需要注意的是假如公告在类上,那么他的子类也会具备动态查找成员的可以力。

@dynamicMemberLookupclass User {    subscript(dynamicMember member: String) -> String {        return "user"    }}class Developer: User { }let dev = Developer()dev.name // "user"


尽管想起来应该是这样,但是还是很反直觉。由于大多数开发者没想过继承一个类后,会有失去属性拼写检查的反作用。这样可可以不小心写错了属性的名字编译器也不会告诉你。
所以公告在类上的时候肯定要特别谨慎。
当然假如想害同事,在BaseViewController里公告是个好主意。

看起来很骚有什么卵使用?

这个特性的感觉就是乍一看很厉害的样子,仔细一看如同就这么回事,再冷静想想似乎没有这么简单。

这个东西本质上只是一个语法糖,和数组的subscript相似。

let numbers = [1, 2]let firstItem = number[0]//这个语法最后还是调使用到了一个方法,假如没有这种写法,相似 oc 的时候就需要显式的调使用一个方法NSNumber *firstItem = [numnber obbjectAtIndex: 0];

原来你需要显式公告字符串参数的地方,能不使用是字符串的形式,能直接使用点语法访问。官方举的例子是 JSON 的用。
常规的写法是这样的:

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

假如像这样定义动态查找成员:

@dynamicMemberLookupenum 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   }}

那么写起来就会是这样:

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

实现方案

这个功可以的实现原理很简单,就是编译器帮助你把点语法转化为下标的语法:

  a = someValue.someMember    //编译器解决后  a = someValue[dynamicMember: "someMember"]

动态属性其实并不陌生,回忆一下 OC 里的属性就是动态合成的。公告了@property后,编译器帮你生成get、set方法。与之相似,在公告了动态查找成员后,编译器帮你转换成了对应的方法。

然而事情并没有这么简单

假如你以为这只是一个语法糖,那你就错了。

独有的身世暴露了你

这个 pr 是由已经离开苹果
加入谷歌的 swift 创始人 CL 提出的。他不仅提了这个 pr,而且还自己实现了。果然是 swift 是亲儿子,身在曹营还不忘为 swift 添砖加瓦。而且大佬不仅提了这个,还提了一个 @dynamicCallable 。
当你给一个对象标记@dynamicCallable后,能动态的给传参。

// 常规操作a = someValue(keyword1: 42, "foo", keyword2: 19)// dynamicallyCalla = someValue.dynamicallyCall(withKeywordArguments: [    "keyword1": 42, "": "foo", "keyword2": 19])

是的,这很 JS。

大佬就是大佬,要啥有啥。目前 @dynamicCallable的进度已经在 review 中,也许 5.0 的时候可以够上?我猜测 swift 团队想这两个特性都开发完后一起宣布所以这次发布会没有详情。

另有所谋:把 Python 和 JS 归入怀中

Swift 目前能”良好“的和 C、OC 交互。然而程序的世界里还有少量重要的动态语言,比方 Python 、 JS,emmm,还有有实力但是不太主流的 Perl、Ruby。假如 swift 可以够愉快的的调使用 Python 和 JS 的库,那么毫无疑问会极大的拓展的 swift 的边界。
这里需要一点想象力,由于这个设计真正的意义是@dynamicMemberLookup@dynamicCallable组合起来使用。通过@dynamicMemberLookup动态的返回一个函数,再通过@dynamicCallable来调使用。从语法层面来讲,这种姿态下 swift 完完全全是一门动态语言

@dynamicCallable @dynamicMemberLookupclass WeiSuoYuWei {}let niuBi = WeiSuoYuWei()niuBi.someMethod.dynamicallyCall(withKeywordArguments: ["wei_suo_yu_wei": true])

就像上面的代码展现的,你不必公告过someMethod也能通过动态特性调使用到,合法的传参。真的能为所欲为!
据说谷歌的 TensorFlow For Swift 可以够顺利的开发就是依靠了这个特性。CL 是这么说的:

While this is a syntactic sugar proposal, we believe that this expands Swift to be usable in important new domains

语法糖上的一小步,swift 的一大步!

reference

How to use Dynamic Member Lookup in Swift – Hacking with Swift
SE 195: Introduce User-defined “Dynamic Member Lookup” Types


  • 微博:@没故事的卓同学

  • 假如想与我有更密切的交流也能加入我的知识星球:程序员生存指南

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 细说 Swift 4.2 新特性:Dynamic Member Lookup

发表回复