Swift 4.2 的新特性這兩篇文章已經介紹的很清楚了:WWDC 2018:Swift 更新了什麼,Swift 4.2 新特性更新。可是 4.2 中實現的 dynamic member lookup 蘋果在 WWDC 上卻徹底沒有提到。然而我認爲這是一個對將來有着重要影響的特性,因此這裏單獨介紹一下。python
這個特性中文能夠叫動態查找成員。在使用@dynamicMemberLookup
標記了對象後(對象、結構體、枚舉、protocol),實現了subscript(dynamicMember member: String)
方法後咱們就能夠訪問到對象不存在的屬性。若是訪問到的屬性不存在,就會調用到實現的 subscript(dynamicMember member: String)
方法,key 做爲 member 傳入這個方法。 好比咱們聲明瞭一個結構體,沒有聲明屬性。git
@dynamicMemberLookup
struct 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
方法能夠返回可空的值。github
這個方法能夠被重載。和泛型的邏輯相似,會根據你要的返回值而經過類型推斷來選擇對應的subscript
方法。json
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> String {
let properties = ["nickname": "Zhuo", "city": "Hangzhou"]
return properties[member, default: "undefined"]
}
subscript(dynamicMember member: String) -> Int {
return 18
}
}
複製代碼
可是執行的時候就必定要告訴編譯器你要獲取的屬性是什麼類型的,不然會編譯錯誤。swift
let p = Person()
let age: Int = p.age
print(age) // 18
複製代碼
Swift 中函數是一等公民,因此返回函數也是能夠的。數組
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> (_ input: String) -> Void {
return {
print("Hello! I live at the address \($0).")
}
}
}
複製代碼
須要注意的是若是聲明在類上,那麼他的子類也會具備動態查找成員的能力。安全
@dynamicMemberLookup
class User {
subscript(dynamicMember member: String) -> String {
return "user"
}
}
class Developer: User { }
let dev = Developer()
dev.name // "user"
複製代碼
雖然想起來應該是這樣,可是仍是很反直覺。由於大多數開發者沒想過繼承一個類後,會有失去屬性拼寫檢查的反作用。這樣可能不當心寫錯了屬性的名字編譯器也不會告訴你。
因此聲明在類上的時候必定要特別謹慎。app
固然若是想害同事,在BaseViewController
裏聲明是個好主意。 dom
這個特性的感受就是乍一看很厲害的樣子,仔細一看好像就這麼回事,再冷靜想一想彷佛沒有這麼簡單。
這個東西本質上只是一個語法糖,和數組的subscript
相似。
let numbers = [1, 2]
let firstItem = number[0]
//這個語法最後仍是調用到了一個方法,若是沒有這種寫法,相似 oc 的時候就須要顯式的調用一個方法
NSNumber *firstItem = [numnber obbjectAtIndex: 0];
複製代碼
原來你須要顯式聲明字符串參數的地方,能夠不用是字符串的形式,能夠直接用點語法訪問。官方舉的例子是 JSON 的使用。 常規的寫法是這樣的:
json[0]?["name"]?["first"]?.stringValue
複製代碼
若是像這樣定義動態查找成員:
@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
}
}
複製代碼
那麼寫起來就會是這樣:
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)
// dynamicallyCall
a = someValue.dynamicallyCall(withKeywordArguments: [
"keyword1": 42, "": "foo", "keyword2": 19
])
複製代碼
是的,這很 JS。
大佬就是大佬,要啥有啥。目前 @dynamicCallable
的進度已經在 review 中,也許 5.0 的時候可以上?我猜想 swift 團隊想這兩個特性都開發完後一塊兒宣佈因此此次發佈會沒有介紹。
Swift 目前能夠」良好「的和 C、OC 交互。然而程序的世界裏還有一些重要的動態語言,好比 Python 、 JS,emmm,還有有實力可是不太主流的 Perl、Ruby。若是 swift 可以愉快的的調用 Python 和 JS 的庫,那麼毫無疑問會極大的拓展的 swift 的邊界。
這裏須要一點想象力,由於這個設計真正的意義是@dynamicMemberLookup
、 @dynamicCallable
組合起來用。經過@dynamicMemberLookup
動態的返回一個函數,再經過@dynamicCallable
來調用。從語法層面來說,這種姿態下 swift 完徹底全是一門動態語言。
@dynamicCallable @dynamicMemberLookup
class 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 的一大步!
How to use Dynamic Member Lookup in Swift – Hacking with Swift
SE 195: Introduce User-defined 「Dynamic Member Lookup」 Types