Swift新特性dynamicMemberLookup和dynamicCallable

[TOC]

參考what's new in swift 5.0細(xì)說 Swift 4.2 新特性:Dynamic Member Lookup

@dynamicMemberLookup

@dynamicMemberLookup是什么

dynamicMemberLookup是Swift4.2里更新的一個(gè)特性翻譯出來就是動(dòng)態(tài)成員查找。在使用@dynamicMemberLookup標(biāo)記了對(duì)象后(對(duì)象、結(jié)構(gòu)體、枚舉、protocol),實(shí)現(xiàn)了subscript(dynamicMember member: String)方法后我們就可以訪問到對(duì)象不存在的屬性。如果訪問到的屬性不存在,就會(huì)調(diào)用到實(shí)現(xiàn)的 subscript(dynamicMember member: String)方法,key 作為 member 傳入這個(gè)方法。

例如:

 @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);

輸出的結(jié)果為 s = "12321321",p = 455

我再這個(gè)類里面并沒有顯示的聲明 name 和 age 這兩個(gè)屬性但是他卻可以得到這兩個(gè)屬性。是因?yàn)楫?dāng)我將這個(gè)類標(biāo)記為 @dynamicMemberLookup 類里面會(huì)實(shí)現(xiàn)subscript (dynamicMember member: String) -> ?這個(gè)方法。

如果沒有聲明@dynamicMemberLookup的話,執(zhí)行的代碼肯定會(huì)編譯失敗。很顯然作為一門類型安全語言,編譯器會(huì)告訴你不存在這些屬性。但是在聲明了@dynamicMemberLookup后,雖然沒有定義 age等屬性,但是程序會(huì)在運(yùn)行時(shí)動(dòng)態(tài)的查找屬性的值,調(diào)用subscript(dynamicMember member: String)方法來獲取值。

這個(gè)屬性可以被重載,會(huì)根據(jù)你要的返回值而通過類型推斷來選擇對(duì)應(yīng)的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)

輸出的結(jié)果為 "Swift","b","undefined",18。 執(zhí)行的時(shí)候一定要告訴編譯器你的常量是什么類型的。

@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

它是將自定義下標(biāo)轉(zhuǎn)換為簡單點(diǎn)語法的語法糖。
其實(shí)相當(dāng)于執(zhí)行了
json[0].name == json[0].subscript(dynamicMember member: "name")

通過這個(gè)方法拿到 json[0]字典key為name對(duì)應(yīng)的值

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

這個(gè)只是簡單的應(yīng)用 在Swift5.0里又推出了dynamicCallable這個(gè)特性??梢詣?dòng)態(tài)的進(jìn)行傳參。

dynamicCallable

@dynamicCallable是什么

SE-0216向@dynamicCallable 添加了一個(gè)新的@dynamicCallable屬性,該屬性帶來了將類型標(biāo)記為可直接調(diào)用的能力。它是語法糖,而不是任何類型的編譯器,有效地轉(zhuǎn)換此代碼:

let result = random(numberOfZeroes: 3)

let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeroes": 3])

之前,在Swift 4.2 中寫了一個(gè)叫做@dynamicMemberLookup的功能。@dynamicCallable是@dynamicMemberLookup的自然擴(kuò)展,@dynamicMemberLookup并且具有相同的目的:使 Swift 代碼更容易與動(dòng)態(tài)語言(如 Python 和 JavaScript)一起工作
要將此功能添加到自己的類里,需要添加@dynamicCallable屬性加上以下一@dynamicCallable種或兩種方法:

func dynamicallyCall(withArguments args: [Int]) -> Double

func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double

第一種是在調(diào)用沒有參數(shù)標(biāo)簽的類型時(shí)使用的,第二種是在提供標(biāo)簽時(shí)a(b, c)使用的(例如a(b: cat, c: dog) ).
@dynamicCallable非常靈活地了解其方法接受和返回的數(shù)據(jù)類型,讓您從 Swift 的所有類型安全性中獲益,同時(shí)仍有一些可高級(jí)使用空間。因此,對(duì)于第一個(gè)方法(沒有參數(shù)標(biāo)簽),您可以使用任何符合ExpressibleByArrayLiteral的任何方法,如數(shù)組、數(shù)組切片和集;對(duì)于第二種方法(帶有參數(shù)標(biāo)簽),您可以使用任何符合ExpressibleByDictionaryLiteral文本,如字典和鍵值對(duì)。

注意:如果您以前沒有使用過KeyValuePairs那么現(xiàn)在正是了解它們的好時(shí)機(jī),因?yàn)樗鼈傽dynamicCallable非常有用。

KeyValuePairs在 Swift 5.0 之前,有點(diǎn)令人困惑地稱為DictionaryLiteral是一種有用的數(shù)據(jù)類型,它提供了類似字典的功能,具有以下幾個(gè)優(yōu)點(diǎn):

  1. 您的密鑰不需要符合Hashable.
  2. 您可以使用重復(fù)的鍵添加項(xiàng)。(不會(huì)覆蓋自定中添加的值)
  3. 添加項(xiàng)的順序?qū)⒈A簟?是DictionAry變有序)

除了接受各種輸入外,您還可以為各種輸出提供多個(gè)重載 - 一個(gè)輸出可以返回一個(gè)字符串,一個(gè)返回一個(gè)整數(shù),等等。只要 Swift 能夠解決使用哪一個(gè),就可以混合和匹配所有您想要的。

下面是一個(gè)例子:

首先,下面是一個(gè)RandomNumberGenerator結(jié)構(gòu),根據(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時(shí)需要注意一些重要的規(guī)則:

  1. 您可以將其應(yīng)用于結(jié)構(gòu)、枚舉、類和協(xié)議。
  2. 如果使用withKeywordArguments:并且不使用withArguments:您的類型仍然可以在沒有參數(shù)標(biāo)簽的情況下調(diào)用 - 您只會(huì)獲得鍵的空字符串。
  3. 如果withKeywordArguments:或與withArguments:被標(biāo)記為throwing,調(diào)用類型也將throwing。
  4. 不能@dynamicCallable添加到擴(kuò)展,只能添加類型的主要定義。
  5. 您仍然可以向類型添加其他方法和屬性,并正常使用它們。

總結(jié)

dynamicMemberLookup是Swift4.2里更新的一個(gè)特性翻譯出來就是動(dòng)態(tài)成員查找。在使用@dynamicMemberLookup標(biāo)記了對(duì)象后(對(duì)象、結(jié)構(gòu)體、枚舉、protocol),實(shí)現(xiàn)了subscript(dynamicMember member: String)方法后我們就可以訪問到對(duì)象不存在的屬性。如果訪問到的屬性不存在,就會(huì)調(diào)用到實(shí)現(xiàn)的 subscript(dynamicMember member: String)方法,key 作為 member 傳入這個(gè)方法。
ynamicCallable屬性,該屬性帶來了將類型標(biāo)記為可直接調(diào)用的能力。它是語法糖

Swift 目前可以”良好“的和 C、OC 交互。然而程序的世界里還有一些重要的動(dòng)態(tài)語言,比如 Python 、 JS,emmm,還有有實(shí)力但是不太主流的 Perl、Ruby。如果 swift 能夠愉快的的調(diào)用 Python 和 JS 的庫,那么毫無疑問會(huì)極大的拓展的 swift 的邊界。
這里需要一點(diǎn)想象力,因?yàn)檫@個(gè)設(shè)計(jì)真正的意義是@dynamicMemberLookup、 @dynamicCallable組合起來用。通過@dynamicMemberLookup動(dòng)態(tài)的返回一個(gè)函數(shù),再通過@dynamicCallable來調(diào)用。從語法層面來講,這種姿態(tài)下 swift 完完全全是一門動(dòng)態(tài)語言。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容