本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。
本章主要是介绍函数、闭包相关的基础知识。如果你对此已有了解可以直接跳过
关于函数,主要有三个重要的概念:
- 函数也是一种类型,和
Int、String类型等类似,函数可以被赋值给变量,也可以作为参数传入、传出别的函数。 - 函数体内,不仅可以使用它的参数,还可以使用函数外部被截获的变量
- 创建函数有两种方法:用
func关键字或闭包表达式{ }
接下来详细解释这三个概念:
1. 函数可以赋值给变量,也可以作为参数传入、传出别的函数
在Swift和很多现代语言中,函数也是“一等公民”。它可以被赋值给变量,也可以作为参数传入、传出别的函数。理解这一点很重要,这是函数式编程的基础,就像指针在C语言中那么重要。我们通过一段代码来演示函数赋值给变量:
//定义了一个函数
func printInt(i: Int) {
print("you passed \(i)")
}
//函数赋值给变量,函数名后不用加括号
let funVar = printInt
//通过函数名调用函数,需要传入参数
funVar(2) //输出结果:“you passed 2”
函数也可以作为别的函数的参数来用:
// 这个函数的参数是一个 (Int) -> ()类型 的函数
func useFunction(funcParam: (Int) -> ()) {
funcParam(3)
}
//把之前的函数传入,作为参数。输出结果相同,都是:“you passed 3”
useFunction(funVar)
useFunction(printInt)
掌握了这一点,我们就可以写出很多有用的高阶函数,这在第三章——“集合”中有所体现。
2. 函数体内可以使用它截获的,位于函数外部的变量
我们先看一个例子,把函数作为另外一个函数的返回值:
// 返回的函数类型是:(Int) -> String
func returnFunc() -> (Int) -> String {
func innerFunc(i: Int) -> String {
return "you passed \(i) to the returned func"
}
return innerFunc
}
let myFnc = returnFunc()
print(myFnc(4))
//输出结果:“you passed 4 to the returned func”
如果函数引用了函数体外部的变量,我们说这个变量被函数“截获”了。变量在超出自己的作用域后不会被释放,因为还有这个函数引用着它。我们把刚刚的函数修改一下,新增一个count变量:
func returnFunc() -> (Int) -> () {
var count = 0
func innerFunc(i: Int) {
count += i
print("running total is now \(i)")
}
return innerFunc
}
每次调用returnFunc函数都会获得一个不同的函数:
let f = returnFunc()
f(3) // 会输出:“running total is now 3”
f(4) // 会输出:“running total is now 7”
let g = returnFunc()
g(2) // 会输出:“running total is now 2”
g(2) // 会输出:“running total is now 4”
f(2) // 会输出:“running total is now 9”
你可以把截获了变量的函数理解为一个类,这个类有一个方法(就是这个函数自身)和多个成员变量(被截获的变量)
3. 闭包表达式的{ }语法
除了用func关键字创建函数之外,还可以用{ }语法创建函数。先看一个普通的函数:
func doubler(i: Int) -> Int { return i * 2 }
let a = [1, 2, 3, 4].map(doubler) //a = [2, 4, 6, 8]
等价的写法是:
let doubler = { (i: Int) -> Int in return i * 3 }
let a = [1, 2, 3, 4].map(doubler) //a = [3, 6, 9, 12]
用闭包表达式定义的函数,可以理解为函数的“字面量”。就像1、"hello"分别是Int、String类型的字面量一样。与用func关键字不同的是,用闭包表达式定义的函数是匿名的。如果想使用它,可以把它赋值给某个变量。
这里我写了return i * 3并非写错,而是表示新定义的doubler会替换之前的doubler,所以输出结果中数组的每个元素都是之前的三倍。这也说明了用func关键字和闭包表达式创建的函数是完全等价的。
闭包表达式的作用主要是在于简化定义函数的方式。比如之前的double可以做如下简化:
let a = [1, 2, 3, 4].map { $0 * 2 } //a = [2, 4, 6, 8]
如果使用闭包表达式,根本没有必要创建doubler函数,就可以完成map。接下来我们以原来复杂的代码为例,一步一步详细描述简化过程:
let doubler = { (i: Int) -> Int in return i * 2 }
let a = [1, 2, 3, 4].map(doubler) //a = [3, 6, 9, 12]
- 函数作为参数传入别的函数中时,没有必要把这个函数先赋值给一个临时变量,再把临时变量传入到别的函数中。我们可以直接把函数写在别的函数中。简化结果:
let a = [1, 2, 3, 4].map({ (i: Int) -> Int in return i * 2 })
- 不必指定编译器能根据上下文推导出的类型。比如在这个例子中,数组的元素是
Int类型,所以传入到map方法中的函数的参数类型也是Int,由于函数中进行了乘法运算,所以返回值类型必然也是Int。简化结果:
let a = [1, 2, 3, 4].map({ i in return i * 2 })
- 如果闭包内只有一条语句,那么显然返回值就是这条语句的值,
return关键字就可以省略。简化结果:
let a = [1, 2, 3, 4].map({ i in i * 2 })
- 对于传入的参数,可以不指定具体的名字。默认就是
$0表示第一个参数,$1表示第二个参数……简化结果:
let a = [1, 2, 3, 4].map({ $0 * 2 })
- 如果作为参数的函数是别的函数的最后一个参数,它可以写在函数的括号外面。这就是“尾闭包”语法。简化结果:
let a = [1, 2, 3, 4].map() { $0 * 2 }
- 因为
map函数没有别的参数,所以可以省略括号。简化结果:
let a = [1, 2, 3, 4].map { $0 * 2 }
这样的写法一开始可能不太熟悉,但是习惯之后就会发现它在不损害代码可读性的前提下,极大的简化了代码量。新手在简化闭包表达式时,可能会遇到各种错误。这时候最好先写出它的完整表达式,然后按照上述规则一步一步简化,直到遇到错误为止。这时再好好考虑当时是哪里犯了错。
标注函数类型
Swift会自动推导闭包表达式的类型,但有时我们也需要显式的标出。比如这个函数:
let isEven = { $0 % 2 == 0 }
它会被自动推导为Int -> Bool类型的。如果我们需要人为指定它的类型,可以这么写:
let isEven = { (i: Int8) -> Bool in i % 2 == 0 }
不过,在闭包内标注类型不是一个好方法,更优雅的解决方案是在闭包外标注类型:
var isEven: Int8 -> Bool = { $0 % 2 == 0 }
// 或者
let isEven = { $0 % 2 == 0 } as Int8 -> Bool
最好的,也是比较复杂的解决方案是定义一个范型的isEven方法:
func isEven<T: IntegerType>(i: T) -> Bool {
return i % 2 == 0
}
let int8isEven: Int8 -> Bool = isEven
总结
用{ }闭包表达式和func关键字创建的都是函数,它们是等价的。而闭包是指截获了外部变量的函数。因此,严格意义上来说闭包和闭包表达式是两个不同的概念。闭包表达式就是函数,而闭包是可以截获外部变量的函数。