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
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++ 中 class
和 struct
的最主要的區別在於成員的預設訪問級別是什麼,而在上面的例子中我們指定了訪問級別。在 C++ 中,物件構造在堆上還是棧上是使用者控制的,這點與 Swift 是不同的。
下面的這個例子顯示,callAsFunction
僅僅是一個語法糖,可以直接用這個名字進行呼叫。
let add2 = Adder(base: 2)
print(add2(6)) // 8
print(add2.callAsFunction(6)) // 8
複製程式碼
Swift Callables
在 Swift 中這種不是函式,但可以當成函式一樣呼叫物件還有哪些呢?我們來盤點一下:
- 函式型別的值,在 Swift 中函式是一等公民,有型別,有值。所以
callAsFunction
也可以被賦值給函式型別,供之後呼叫,這不是針對callAsFunction
的新特性,對於其他名稱的方法也可以這樣。
let f: (Int) -> Int = add2.callAsFunction(_:)
print(f(6)) // 8
複製程式碼
-
型別名稱,我們經常使用
UIView(...)
形式來構造物件,但實際上這個語法糖會被轉換成函式呼叫UIView.init(...)
-
@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
可以看作是這種形式的靜態小夥伴。