『闭包』是独立的代码块,可以在代码中随意传递和使用 。Swift 中的闭包与 Objective-C/C 中的 Block、其他编程语言中的匿名函数相似。
全局和嵌套函数实际上也是特殊的闭包。闭包采取如下三种形式之一:
- 全局函数是一个有名字但不会捕获任何值的闭包。
- 嵌套函数是一个有名字并且可以捕获其封闭函数域内值的闭包。
- 闭包表达式是一个用轻量语法所写的可以捕获其上下文中变量或常量值的匿名闭包。
全局函数 | 嵌套函数 | 闭包表达式 |
---|---|---|
名字但不会捕获任何值的闭包 | 有名字并且可以捕获其封闭函数域内值的闭包 | 用轻量语法所写的可以捕获其上下文中变量或常量值的匿名闭包 |
闭包表达式
语法
Swift 中的闭包表达式很灵活,其标准语法格式如下:
{
(参数列表) -> 返回值类型 in
函数体代码
}
其中,参数列表与函数中的参数列表形式一样,返回值类型类似于函数中的返回值类型,但不同的是后面有in关键字(分割返回值类型和函数体代码)。
示例:
var sum = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
sum(1, 2)
输出:3
复制代码
可以看到上面的闭包表达式其实和通过 func
定义的函数很像,也实现同样的功能 func
定义的函数
func sumFunc(v1: Int, v2: Int) -> Int {
return v1 + v2
}
sumFunc(v1: 1, v2: 2)
复制代码
闭包表达式的简写 Swift 提供了多种闭包简化写法
定义函数,接受三个参数,其中第三个参数为一个函数类型,函数内调用第三个参数(即传入的那个函数),并计算传入的两个参数之和
func sumFunc(v1: Int, v2: Int, sum: (Int, Int) -> Int) {
print(sum(v1, v2))
}
复制代码
调用sumFunc,传入第三个参数,通过标准闭包表达式传入
sumFunc(v1: 1, v2: 2, sum: {
(number1: Int, number2: Int) -> Int in
return number1 + number2
})
最终打印:3
复制代码
类型推断简化
由于在调用函数传入闭包表达式参数时,形参的sum参数可以推断入参和返回值是 Int,则可以简化
sumFunc(v1: 1, v2: 2, sum: {
number1, number2 in
return number1 + number2
})
输出:3
复制代码
隐藏return关键字(单表达式闭包隐式返回)
如果在闭包内部语句组只有一条语句,如return a + b等,那么这种语句都是返回语句。前面的关键字return可以省略,省略形式如下: {number1, number1 in number1 + number1 } 所以:
sumFunc(v1: 1, v2: 2, sum: {
number1, number2 in
number1 + number2
})
输出:3
复制代码
需要注意的是,省略的前提是闭包中只有一条 return
语句:number1 + number2
。下面这样有多条语句是不允许的。
缩写参数名称
Swift提供了参数名称缩写功能,我们可以用0、0、0、1、2来表示调用闭包中参数,2来表示调用闭包中参数,2来表示调用闭包中参数,0指代第一个参数,1指代第二个参数,1指代第二个参数,1指代第二个参数,2指代第三个参数,以此类推n+1指代第n个参数。 使用参数名称缩写,还可以在闭包中省略参数列表的定义,Swift能够推断出这些缩写参数的类型。此外,in关键字也可以省略。参数名称缩写之后如下所示: {0 + $1}
sumFunc(v1: 1, v2: 2, sum: { $0 + $1 })
输出:3
复制代码
运算符方法 实际上还有一种 更简短 的方式来编写上面例子中的闭包表达式。Swift 的 Int 类型 的 + 可以直接缩写为 + 进一步缩写后:
sumFunc(v1: 1, v2: 2, sum: +)
输出:3
复制代码
尾随闭包
如果将一个很长的闭包表达式做为函数的最后一个参数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,不用写出它的参数标签
定义函数:
func funcClosure(closure: () -> Void) {
// 函数体部分
}
// 不使用尾随闭包进行函数调用
funcClosure(closure: {
// 闭包主体部分
})
// 使用尾随闭包进行函数调用
funcClosure() {
// 闭包主体部分
}
复制代码
同样以上面 sumFunc
为例:
sumFunc(v1: 1, v2: 2) {
$0 + $1
}
复制代码
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:
func addFunc(add: (Int, Int) -> Int) {
print(add(1, 2))
}
addFunc {
$0 + $1
}
复制代码
闭包值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
示例 函数 summationFunc(一个参数,返回值为一个函数) 内部嵌套函数 summation(无参,返回值为整型)。
嵌套函数 summation() 捕获了两个值,number 和 result。捕获这两个值之后,外层函数 summationFunc 将 嵌套函数 summation 作为闭包返回。每次调用 summation 时,其会以 number 作为增量增加到 result。
ps;iOS开发交流技术群:欢迎你的加入,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
let v3 = function()
print("v1=\(v1), v2=\(v2), v3=\(v3)")
let function2 = summationFunc(number: 10)
let v4 = function2()
let v5 = function2()
let v6 = function2()
print("v4=\(v4), v5=\(v5), v6=\(v6)")
复制代码
输出信息:
v1=12, v2=22, v3=32
v4=12, v5=22, v6=32
复制代码
ps;iOS开发交流技术群:欢迎你的加入,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长
可以看到程序可以正常运行,且function 和 function2 每次调用会分别累加。 我们知道一般情况下 函数的形参和函数中的局部变量存储在栈取,在函数调用结束后就会被销毁。而此处的函数 summationFunc 和我们想象的不一样。 对于嵌套函数:
func summation() -> Int {
result += number
return result
}
复制代码
函数没有任何参数,函数体内却访问了的 result 和 number 两个变量。 这是因为它捕获了 result 和 number 变量的引用。捕获引用保证了 result 和 number 变量在调用完 summationFunc 后不会消失,并且保证了在下一次执行 summation 函数时,两个变量依旧存在。
断点调试可以发现该闭包调用了 swift_allocObject
,创建了对象存储在堆中,也就是说在堆中创建了一个对象用于存储 result。闭包准确的数应该描述为函数连同其捕获的变量(或常量)的组合。
捕获上下文变量(常量)的时机:
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
result = 200
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
print("v1=\(v1), v2=\(v2)")
复制代码
输出:
v1=30, v2=40
复制代码
debug 可以看到,在返回之前进行捕获,且捕获了两次(每赋值一次就捕获一次),且最终存储的是最后一次赋的值
闭包是引用类型
上面的例子中,function 和 function1 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用 function(或function1) 是一个常量,而并非闭包内容本身。 这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包: 例如将上述 function 赋值给 testFunction,则继续调用
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
let v3 = function()
print("v1=\(v1), v2=\(v2), v3=\(v3)")
let testFunction = function
let t1 = function()
let t2 = function()
let t3 = function()
print("t1=\(t1), t2=\(t2), t3=\(t3)")
复制代码
打印信息:
v1=12, v2=22, v3=32
t1=42, t2=52, t3=62