乡亲们我回来了
一年写一次,一次写一批,一批写完后休息一年,在 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++ 中 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, 2, 3]
x(Leon: 28, 178) // ["Leon": 28, "": 178]
从某种程度上来讲,callAsFunction
可以看作是这种形式的静态小伙伴。
扫码下方二维码,关注“面试官小健”