首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》2.10 将函数作为值

关灯直达底部

如果从未使用过将函数当作一等公民的编程语言,那么你现在应该坐好了,因为我所说的话可能会让你昏倒:在Swift中,函数是一等公民。这意味着函数可以用在任何可以使用值的地方。比如,函数可以赋给变量;函数可以作为函数调用的参数;函数可以作为函数的结果返回。

Swift有严格的类型。你只能将一个值赋给变量,或是将值传递给函数以及从函数传递出来,前提是它的类型是正确的。为了将函数当成值来使用,函数需要有一个类型。事实上,函数就是有类型的。能猜出是什么吗?函数的签名就是其类型。

将函数当作值的主要目的在于稍后可以在不知道函数是什么的情况下调用该函数。

下面是个简单的示例,只展示了语法与结构:


func doThis(f:->) {    f}  

doThis函数接收一个参数,并且无返回值。参数f本身是个函数;因为参数类型不是Int、String或Dog,而是一个函数签名()->(),这表示一个不接收参数、无返回值的函数。接下来,doThis函数会调用接收到的函数f作为其参数,这正是函数体中参数名后面圆括号的含义。

那么该如何调用函数doThis呢?你需要传递一个函数作为实参。一种方式是将函数名作为参数,如以下代码所示:


func whatToDo {    print(/"I did it/")}doThis(whatToDo)  

首先,我们声明了一个恰当类型的函数,该函数不接收参数且无返回值。接下来调用doThis,将函数名作为实参传递给它。注意,这里并没有调用whatToDo,只是将其传递进去。这是因为其名字后面并没有小括号。当然,这么做没问题:将whatToDo作为实参传递给doThis;doThis会将接收到的函数作为参数进行调用;然后控制台会打印出/"I did it/"。

不过,这么做的意义何在?如果目标是调用whatToDo,那为何不直接调用它呢?让其他函数调用它有什么好处呢?在刚才给出的示例中看不出来这么做的好处;我只是介绍一下语法与结构。不过在实际情况下,这么做是很有价值的,因为其他函数可以以特殊的方式来调用参数函数。比如,可以在完成其他一些事情后再调用它,或稍后再调用。

比如,将函数调用封装到函数中的一个原因是可以减少重复,降低出错的可能。如下示例来自于我所编写的代码。Cocoa中常做的一件事就是在代码中直接绘图,这涉及4个步骤:


let size = CGSizeMake(45,20)UIGraphicsBeginImageContextWithOptions(size, false, 0) ①let p = UIBezierPath(    roundedRect: CGRectMake(0,0,45,20), cornerRadius: 8)p.stroke ②let result = UIGraphicsGetImageFromCurrentImageContext ③UIGraphicsEndImageContext ④  

①打开图形上下文。

②在上下文中绘制。

③提取图像。

④关闭图形上下文。

这么做丑陋至极。所有代码的唯一目的就是获取result,即图像;不过这个目的散落到了其他代码中。同时,整个结构是样本式的;无论在哪个应用中,步骤1、步骤3和步骤4都是一样的。此外,我很担心会忘记其中某一步;比如,如果不小心漏掉了步骤4,那么结果就会非常糟糕。

每次绘制时唯一不同的就是步骤2。因此,步骤2是唯一一个需要编写的部分!只要编写一个辅助函数来表示出这个样板化过程就能解决所有问题:


func imageOfSize(size:CGSize, whatToDraw: -> ) -> UIImage {    UIGraphicsBeginImageContextWithOptions(size, false, 0)    whatToDraw    let result = UIGraphicsGetImageFromCurrentImageContext    UIGraphicsEndImageContext    return result}  

imageOfSize辅助函数很有用,因此我将其声明在文件顶层,这样所有文件就都能看到它了。为了绘制图像,我在函数中执行了步骤2(实际的绘制),然后将该函数作为实参传递给imageOfSize辅助函数:


func drawing {    let p = UIBezierPath(        roundedRect: CGRectMake(0,0,45,20), cornerRadius: 8)    p.stroke}let image = imageOfSize(CGSizeMake(45,20), drawing)  

这是将绘制指令转换为图像的一种漂亮的表示方式。

Cocoa API很多时候都需要传递函数,然后由运行时以某种方式或稍后调用。比如,当一个视图控制器展现视图时,你所调用的方法会接收3个参数:展现的视图控制器、表示展现是否要添加动画的Bool值,以及展现完毕后所调用的函数:


let vc = UIViewControllerfunc whatToDoLater {    print(/"I finished!/")}self.presentViewController(vc, animated:true, completion:whatToDoLater)  

Cocoa文档常常将这样的函数描述为处理器,并将其称作块,因为这里需要的是Objective-C语法结构;在Swift中,它是个函数,因此将其当作函数并传递就可以了。

有些常见的Cocoa场景甚至会将两个函数传递给一个函数。比如,在执行视图动画时,你常常会传递两个函数,一个函数规定动画动作,另一个函数指定接下来要做的事情:


func whatToAnimate { // self.myButton is a button in the interface    self.myButton.frame.origin.y += 20}func whatToDoLater(finished:Bool) {    print(/"finished: (finished)/")}UIView.animateWithDuration(    0.4, animations: whatToAnimate, completion: whatToDoLater)  

这表示:改变界面中按钮的帧原点(即位置),动作持续时间为0.4秒;接下来,当完成时,在控制台中打印出一条日志消息,说明动画执行是否完成。

为了让函数类型说明符更加清晰,请通过Swift的typealias特性创建一个类型别名,为函数类型赋予一个名字。这个名字可以是描述性的,请不要与箭头运算符符号搞混。比如,如果定义typealias VoidVoidFunction=()->(),那就可以在通过该签名指定函数类型时使用VoidVoidFunction了。