Swift新特性 dynamicMemberLookup和dynamicCallable

[TOC]git

參考what's new in swift 5.0細說 Swift 4.2 新特性:Dynamic Member Lookupgithub

@dynamicMemberLookup

@dynamicMemberLookup是什麼

dynamicMemberLookup是Swift4.2裏更新的一個特性翻譯出來就是動態成員查找。在使用@dynamicMemberLookup標記了對象後(對象、結構體、枚舉、protocol),實現了subscript(dynamicMember member: String)方法後咱們就能夠訪問到對象不存在的屬性。若是訪問到的屬性不存在,就會調用到實現的 subscript(dynamicMember member: String)方法,key 做爲 member 傳入這個方法。json

例如:swift

@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 類裏面會實現**subscript (dynamicMember member: String) -> ?**這個方法。安全

若是沒有聲明@dynamicMemberLookup的話,執行的代碼確定會編譯失敗。很顯然做爲一門類型安全語言,編譯器會告訴你不存在這些屬性。可是在聲明瞭@dynamicMemberLookup後,雖然沒有定義 age等屬性,可是程序會在運行時動態的查找屬性的值,調用subscript(dynamicMember member: String)方法來獲取值。bash

這個屬性能夠被重載,會根據你要的返回值而經過類型推斷來選擇對應的subscript方法。例如app

@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。 執行的時候必定要告訴編譯器你的常量是什麼類型的。dom

@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
複製代碼

它是將自定義下標轉換爲簡單點語法的語法糖。 其實至關於執行了 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這個特性。能夠動態的進行傳參。

dynamicCallable

@dynamicCallable是什麼

SE-0216向@dynamicCallable 添加了一個新的@dynamicCallable屬性,該屬性帶來了將類型標記爲可直接調用的能力。它是語法糖,而不是任何類型的編譯器,有效地轉換此代碼:

let result = random(numberOfZeroes: 3)

let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeroes": 3])
複製代碼

以前,在Swift 4.2 中寫了一個叫作@dynamicMemberLookup的功能。@dynamicCallable是@dynamicMemberLookup的天然擴展,@dynamicMemberLookup而且具備相同的目的:使 Swift 代碼更容易與動態語言(如 Python 和 JavaScript)一塊兒工做 要將此功能添加到本身的類裏,須要添加@dynamicCallable屬性加上如下一@dynamicCallable種或兩種方法:

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

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

複製代碼

第一種是在調用沒有參數標籤的類型時使用的,第二種是在提供標籤時a(b, c)使用的(例如a(b: cat, c: dog) ). @dynamicCallable很是靈活地瞭解其方法接受和返回的數據類型,讓您從 Swift 的全部類型安全性中獲益,同時仍有一些可高級使用空間。所以,對於第一個方法(沒有參數標籤),您可使用任何符合ExpressibleByArrayLiteral的任何方法,如數組、數組切片和集;對於第二種方法(帶有參數標籤),您可使用任何符合ExpressibleByDictionaryLiteral文本,如字典和鍵值對。

注意:若是您之前沒有使用過KeyValuePairs那麼如今正是瞭解它們的好時機,由於它們@dynamicCallable很是有用。

KeyValuePairs在 Swift 5.0 以前,有點使人困惑地稱爲DictionaryLiteral是一種有用的數據類型,它提供了相似字典的功能,具備如下幾個優勢:

  1. 您的密鑰不須要符合Hashable.
  2. 您可使用重複的鍵添加項。(不會覆蓋自定中添加的值)
  3. 添加項的順序將保留。(是DictionAry變有序)

除了接受各類輸入外,您還能夠爲各類輸出提供多個重載 - 一個輸出能夠返回一個字符串,一個返回一個整數,等等。只要 Swift 可以解決使用哪個,就能夠混合和匹配全部您想要的。

下面是一個例子:

首先,下面是一個RandomNumberGenerator結構,根據傳入的輸入,生成介於 0 和特定最大值之間的數字:

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編寫相似內容:

@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時須要注意一些重要的規則:

  1. 您能夠將其應用於結構、枚舉、類和協議。
  2. 若是使用**withKeywordArguments:而且不使用withArguments:**您的類型仍然能夠在沒有參數標籤的狀況下調用 - 您只會得到鍵的空字符串。
  3. 若是withKeywordArguments:或與withArguments:被標記爲throwing,調用類型也將throwing
  4. 不能@dynamicCallable添加到擴展,只能添加類型的主要定義。
  5. 您仍然能夠向類型添加其餘方法和屬性,並正常使用它們。

總結

dynamicMemberLookup是Swift4.2裏更新的一個特性翻譯出來就是動態成員查找。在使用@dynamicMemberLookup標記了對象後(對象、結構體、枚舉、protocol),實現了subscript(dynamicMember member: String)方法後咱們就能夠訪問到對象不存在的屬性。若是訪問到的屬性不存在,就會調用到實現的 subscript(dynamicMember member: String)方法,key 做爲 member 傳入這個方法。 ynamicCallable屬性,該屬性帶來了將類型標記爲可直接調用的能力。它是語法糖

Swift 目前能夠」良好「的和 C、OC 交互。然而程序的世界裏還有一些重要的動態語言,好比 Python 、 JS,emmm,還有有實力可是不太主流的 Perl、Ruby。若是 swift 可以愉快的的調用 Python 和 JS 的庫,那麼毫無疑問會極大的拓展的 swift 的邊界。 這裏須要一點想象力,由於這個設計真正的意義是@dynamicMemberLookup、 @dynamicCallable組合起來用。經過@dynamicMemberLookup動態的返回一個函數,再經過@dynamicCallable來調用。從語法層面來說,這種姿態下 swift 完徹底全是一門動態語言。

相關文章
相關標籤/搜索