[TOC]
參考what's new in swift 5.0和細說 Swift 4.2 新特性:Dynamic Member Lookup
@dynamicMemberLookup
@dynamicMemberLookup是什么
dynamicMemberLookup是Swift4.2里更新的一個特性翻譯出來就是動態(tài)成員查找葡盗。在使用@dynamicMemberLookup標記了對象后(對象偷卧、結構體拯爽、枚舉锅纺、protocol)管行,實現(xiàn)了subscript(dynamicMember member: String)方法后我們就可以訪問到對象不存在的屬性。如果訪問到的屬性不存在,就會調(diào)用到實現(xiàn)的 subscript(dynamicMember member: String)方法轿曙,key 作為 member 傳入這個方法。
例如:
@dynamicMemberLookup
class Test {
subscript (dynamicMember member: String) -> String {
return "12321321"
}
subscript (dynamicMember member: String) -> Int {
return 455
}
}
let t = Test()
var s:String = t.name
var p: Int = t.age
print(s);
print(p);
輸出的結果為 s = "12321321",p = 455
我再這個類里面并沒有顯示的聲明 name 和 age 這兩個屬性但是他卻可以得到這兩個屬性。是因為當我將這個類標記為 @dynamicMemberLookup 類里面會實現(xiàn)subscript (dynamicMember member: String) -> 背捌?這個方法续挟。
如果沒有聲明@dynamicMemberLookup的話,執(zhí)行的代碼肯定會編譯失敗碑幅。很顯然作為一門類型安全語言,編譯器會告訴你不存在這些屬性。但是在聲明了@dynamicMemberLookup后陌僵,雖然沒有定義 age等屬性,但是程序會在運行時動態(tài)的查找屬性的值创坞,調(diào)用subscript(dynamicMember member: String)方法來獲取值碗短。
這個屬性可以被重載,會根據(jù)你要的返回值而通過類型推斷來選擇對應的subscript方法摆霉。例如
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> String {
let properties = ["name": "Swift", "city": "B"]
return properties[member, default: ""]
}
subscript(dynamicMember member: String) -> Int {
return 18
}
}
let p = Person()
/***聲明常量必須聲明類型*/
let test:String = p.k;
print(p.nickname)
print(p.city)
print(test);
print(p.age)
輸出的結果為 "Swift","b","undefined",18豪椿。 執(zhí)行的時候一定要告訴編譯器你的常量是什么類型的。
@dynamicMemberLookup有啥用
我們知道了dynamicMemberLookup是什么怎么用携栋,但是蘋果為啥要推出這樣一種語法糖搭盾。
官方給出的例子是這樣的
@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里面的值則需要
let json = JSON.stringValue("Example")
json[0]?["name"]?["first"]?.stringValue
但是聲明dynamicLookUp的就可以這樣使用
json[0]?.name?.first?.stringValue
它是將自定義下標轉換為簡單點語法的語法糖。
其實相當于執(zhí)行了
json[0].name == json[0].subscript(dynamicMember member: "name")
通過這個方法拿到 json[0]字典key為name對應的值
subscript(dynamicMember member: String) -> JSON? {
if case .dictionaryValue(let dict) = self {
return dict[member]
}
return nil
}
這個只是簡單的應用 在Swift5.0里又推出了dynamicCallable這個特性婉支⊙煊纾可以動態(tài)的進行傳參。
dynamicCallable
@dynamicCallable是什么
SE-0216向@dynamicCallable 添加了一個新的@dynamicCallable屬性,該屬性帶來了將類型標記為可直接調(diào)用的能力向挖。它是語法糖,而不是任何類型的編譯器,有效地轉換此代碼:
let result = random(numberOfZeroes: 3)
let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeroes": 3])
之前,在Swift 4.2 中寫了一個叫做@dynamicMemberLookup的功能蝌以。@dynamicCallable是@dynamicMemberLookup的自然擴展,@dynamicMemberLookup并且具有相同的目的:使 Swift 代碼更容易與動態(tài)語言(如 Python 和 JavaScript)一起工作
要將此功能添加到自己的類里,需要添加@dynamicCallable屬性加上以下一@dynamicCallable種或兩種方法:
func dynamicallyCall(withArguments args: [Int]) -> Double
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double
第一種是在調(diào)用沒有參數(shù)標簽的類型時使用的,第二種是在提供標簽時a(b, c)使用的(例如a(b: cat, c: dog) ).
@dynamicCallable非常靈活地了解其方法接受和返回的數(shù)據(jù)類型,讓您從 Swift 的所有類型安全性中獲益,同時仍有一些可高級使用空間。因此,對于第一個方法(沒有參數(shù)標簽),您可以使用任何符合ExpressibleByArrayLiteral的任何方法,如數(shù)組何之、數(shù)組切片和集;對于第二種方法(帶有參數(shù)標簽),您可以使用任何符合ExpressibleByDictionaryLiteral文本,如字典和鍵值對跟畅。
注意:如果您以前沒有使用過KeyValuePairs那么現(xiàn)在正是了解它們的好時機,因為它們@dynamicCallable非常有用。
KeyValuePairs在 Swift 5.0 之前,有點令人困惑地稱為DictionaryLiteral是一種有用的數(shù)據(jù)類型,它提供了類似字典的功能,具有以下幾個優(yōu)點:
- 您的密鑰不需要符合Hashable.
- 您可以使用重復的鍵添加項溶推。(不會覆蓋自定中添加的值)
- 添加項的順序將保留徊件。(是DictionAry變有序)
除了接受各種輸入外,您還可以為各種輸出提供多個重載 - 一個輸出可以返回一個字符串,一個返回一個整數(shù),等等。只要 Swift 能夠解決使用哪一個,就可以混合和匹配所有您想要的蒜危。
下面是一個例子:
首先,下面是一個RandomNumberGenerator結構,根據(jù)傳入的輸入,生成介于 0 和特定最大值之間的數(shù)字:
struct RandomNumberGenerator {
func generate(numberOfZeroes: Int) -> Double {
let maximum = pow(10, Double(numberOfZeroes))
return Double.random(in: 0...maximum)
}
}
let random = RandomNumberGenerator()
let result = random.generate(numberOfZeroes: 0)
要將其切換到@dynamicCallable我們將@dynamicCallable編寫類似內(nèi)容:
@dynamicCallable
struct RandomNumberGenerator {
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double {
let numberOfZeroes = Double(args.first?.value ?? 0)
let maximum = pow(10, numberOfZeroes)
return Double.random(in: 0...maximum)
}
}
let random = RandomNumberGenerator()
/// numberOfZeroes 可以自定義
/// let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeroes": 3])
/// let result = random(numberOfZeroes: 3)
let result = random(numberOfZeroes: 0)
@dynamicCallable使用注意
@dynamicCallable時需要注意一些重要的規(guī)則:
- 您可以將其應用于結構虱痕、枚舉、類和協(xié)議辐赞。
- 如果使用withKeywordArguments:并且不使用withArguments:您的類型仍然可以在沒有參數(shù)標簽的情況下調(diào)用 - 您只會獲得鍵的空字符串部翘。
- 如果withKeywordArguments:或與withArguments:被標記為throwing,調(diào)用類型也將throwing。
- 不能@dynamicCallable添加到擴展,只能添加類型的主要定義响委。
- 您仍然可以向類型添加其他方法和屬性,并正常使用它們新思。
總結
dynamicMemberLookup是Swift4.2里更新的一個特性翻譯出來就是動態(tài)成員查找窖梁。在使用@dynamicMemberLookup標記了對象后(對象、結構體表牢、枚舉窄绒、protocol),實現(xiàn)了subscript(dynamicMember member: String)方法后我們就可以訪問到對象不存在的屬性崔兴。如果訪問到的屬性不存在彰导,就會調(diào)用到實現(xiàn)的 subscript(dynamicMember member: String)方法,key 作為 member 傳入這個方法敲茄。
ynamicCallable屬性,該屬性帶來了將類型標記為可直接調(diào)用的能力位谋。它是語法糖
Swift 目前可以”良好“的和 C、OC 交互堰燎。然而程序的世界里還有一些重要的動態(tài)語言掏父,比如 Python 、 JS秆剪,emmm赊淑,還有有實力但是不太主流的 Perl、Ruby仅讽。如果 swift 能夠愉快的的調(diào)用 Python 和 JS 的庫陶缺,那么毫無疑問會極大的拓展的 swift 的邊界。
這里需要一點想象力洁灵,因為這個設計真正的意義是@dynamicMemberLookup饱岸、 @dynamicCallable組合起來用。通過@dynamicMemberLookup動態(tài)的返回一個函數(shù)徽千,再通過@dynamicCallable來調(diào)用苫费。從語法層面來講,這種姿態(tài)下 swift 完完全全是一門動態(tài)語言双抽。