1. 程式人生 > Android開發 >WWDC 20 前你應該知道的 Swift 新特性:callAsFunction

WWDC 20 前你應該知道的 Swift 新特性:callAsFunction

鄉親們我回來了

一年寫一次,一次寫一批,一批寫完後休息一年,在 WWDC 20 前又回來了,因為又有新素材了嘛。Swift 在 5.1 之後逐漸進入成熟期,所以 Swift 5.2 中的語言新特性多數是小修小補,既然這些特性在最新的 Xcode 正式版已經可以用了,我們不妨一起了解一下吧。本期主題:callAsFunction

遙想當年

遙想當年在大一的 C++ 課堂上老師介紹的 function call operator,哇,真是神奇,一個物件可以當成函式那樣呼叫呢!它還有一個好聽?的名字叫做函式物件,也有的叫做函式子(functor)。先來一段 C++ 回味一下。

struct Adder
{
public:
  Adder(int base):m_base(base){}
  int operator()(int a) {
    return
m_base + a; } private: int m_base; }; int main(int argc,const char * argv[]) { int adder = Adder(2); cout << adder(6) << endl; //8 return 0; } 複製程式碼

函式就是函式,為什麼需要搞得那麼複雜呢?原因是與普通的函式不同, Adder 是帶狀態的。後來的 C++ 11 中引入了 lambda 特性(類似於 Swift 的 closure),它的一種常見實現方式,就是編譯成上面的函式物件。

上世紀的特性

如同 C++ 98 中的 函式物件,Swift 5.2 中的 callAsFunction

是個徹頭徹尾的上世紀特性。Kotlin 中也有類似的 invoke operator,所以對 Swift 來說不是啥發明,只是下定決心把這個小小語法糖給加上了。我們來看一下 Swift 的版本:

struct Adder {
    var base: Int
    func callAsFunction(_ x: Int) -> Int {
        return base + x
    }
}

let add2 = Adder(base: 2)
print(add2(6)) // 8
複製程式碼

這裡的 callAsFunction(...)函式,就是Swift 編譯器約定的名字,這個函式可以有 0-N 個引數,甚至不定參也可以的。除了函式名字的約定,基本上沒有任何可以限制你發揮的地方,你可以加上泛型,甚至可以用 class

或者 enum,當然這種物件一般是值型別語義,所以型別定義成struct是最常見的,其次是enum也可以的,而定義成class 的話一要記得引用型別的開銷,二是必須給它新增init建構函式。

好學的寶寶可能要問為什麼上面 C++ 的例子可不可以是class呢?其實,在 C++ 的例子中使用 class 或者 struct 是一樣的,C++ 中 classstruct 的最主要的區別在於成員的預設訪問級別是什麼,而在上面的例子中我們指定了訪問級別。在 C++ 中,物件構造在堆上還是棧上是使用者控制的,這點與 Swift 是不同的。

下面的這個例子顯示,callAsFunction僅僅是一個語法糖,可以直接用這個名字進行呼叫。

let add2 = Adder(base: 2)
print(add2(6)) // 8
print(add2.callAsFunction(6)) // 8
複製程式碼

Swift Callables

在 Swift 中這種不是函式,但可以當成函式一樣呼叫物件還有哪些呢?我們來盤點一下:

  1. 函式型別的值,在 Swift 中函式是一等公民,有型別,有值。所以callAsFunction也可以被賦值給函式型別,供之後呼叫,這不是針對callAsFunction的新特性,對於其他名稱的方法也可以這樣。
let f: (Int) -> Int = add2.callAsFunction(_:)
print(f(6)) // 8
複製程式碼
  1. 型別名稱,我們經常使用 UIView(...) 形式來構造物件,但實際上這個語法糖會被轉換成函式呼叫 UIView.init(...)

  2. @dynamicCallable 修飾的型別,多用於與動態語言的互動。

@dynamicCallable struct DummyCallable {
    func dynamicallyCall(withArguments: [Int]) {
      print(withArguments)
  }
    func dynamicallyCall(withKeywordArguments: KeyValuePairs<String,Int>) {
      print(withKeywordArguments)
  }
}

let x = DummyCallable()
x(1,2,3) // [1,3]
x(Leon: 28,178) // ["Leon": 28,"": 178]
複製程式碼

從某種程度上來講,callAsFunction 可以看作是這種形式的靜態小夥伴。