闭包的定义
闭包(Closure) 是一种能够捕获和存储其所在上下文中的变量和常量的独立代码块。换句话说,闭包可以在定义时捕获其作用域内的变量,并在闭包的生命周期内使用和修改这些变量。
闭包在 Swift 中的表现有点像匿名函数,但更强大。它们可以作为参数传递,也可以作为返回值返回,还可以持有并修改其作用域内的变量,即使作用域已经销毁,闭包仍然可以访问这些变量。
闭包的三种形式
在 Swift 中,闭包有三种主要的形式:
1、全局函数:有名字并且不会捕获任何值的闭包。
全局函数是定义在全局范围内的函数,它们有名字,并且不会捕获任何值。这些函数可以在代码的任何地方使用,因为它们存在于全局作用域中。
func greet(name: String) -> String {
return "Hello, \(name)!"
}
这里的 greet 就是一个全局函数。它不会捕获任何外部变量,因为它定义在全局范围内。
上面的全局函数在语法上是普通的命名函数,但实际上,也属于闭包的一种形式。
广义上来讲,闭包是一个功能块,可以被传递和使用。根据Swift文档的定义,闭包是一个可以捕获和存储外部作用域中的变量的字包含代码块。
2、嵌套函数:有名字并且可以捕获其上层函数中的值。
嵌套函数是定义在另一个函数内部的函数。它们可以捕获并存储外层函数中的变量。这使得嵌套函数在保持状态信息或创建一些专用的逻辑时非常有用。
func makeMultiplier(by multiplier: Int) -> (Int) -> Int {
func multiplierFunction(number: Int) -> Int {
return number * multiplier
}
return multiplierFunction
}
let multiplyByTwo = makeMultiplier(by: 2)
print(multiplyByTwo(3)) // 输出: 6
在这个例子中,multiplierFunction 是 makeMultiplier(by:) 的嵌套函数。由于 multiplierFunction 是在 makeMultiplier 内部定义的,它可以捕获 multiplier 的值并在执行时使用它。
3、闭包表达式:没有名字的闭包,可以捕获其上下文中的值。
闭包表达式是一段没有名字的代码块,可以捕获其所在上下文中的变量。它们是一种更灵活的匿名函数,可以用简洁的语法进行定义。闭包表达式非常适合用于需要传递函数作为参数的场景。
let add: (Int, Int) -> Int = { (a, b) in
return a + b
}
print(add(3, 4)) // 输出: 7
在这个例子中,add 是一个变量,类型是 (Int, Int) -> Int,表示一个接受两个 Int 参数并返回一个 Int 值的闭包。
{ (a, b) in return a + b } 是一个匿名闭包,可以直接赋值给 add 变量。
(a, b) 是闭包的参数列表,-> Int 表示闭包的返回类型,in 之后是闭包的代码体。
实际上,上面的闭包表达式等价于:
func add(a: Int, b: Int) -> Int {
return a + b
}
print(add(3, 4)) // 输出: 7
另外,闭包表达式的参数括号是可选的:
let add: (Int, Int) -> Int = { a, b in
return a + b
}
这里,a 和 b 是参数,直接用逗号分隔。
Swift 允许省略参数的括号,直接写成 a, b in,当参数数量明确且类型已指定时,编译器可以正确解析参数类型。
加括号 () 和不加括号对代码的功能没有影响,都是正确的闭包表达方式。在参数简单、数量少时可以省略括号,复杂情况时建议保留括号,以增加可读性。
闭包表达式和普通函数的区别
1、语法简洁度
闭包表达式:闭包更为灵活简洁,允许使用尾随闭包、省略参数名等语法糖,使其在某些上下文中更简洁和易于使用。例如,{ a, b in a + b } 就是完全简化后的闭包形式。
普通函数:普通函数的声明相对较正式,需要明确的函数名称、参数名和类型。
2、命名要求
闭包表达式:可以是匿名的。闭包通常在局部范围内定义并立即使用,因此不需要名称(可以将其直接赋给变量来调用)。
let numbers = [3, 1, 4, 2]
let sortedNumbers = numbers.sorted { $0 < $1 } // 直接传入闭包
这里的闭包 { $0 < $1 } 只用于 sorted 函数,不需要给闭包命名。闭包定义在这个局部范围内,任务完成后不再使用。
普通函数:必须有名称,适合在多处调用的情景。
3、捕获上下文(环境)
闭包表达式:闭包可以捕获其所在上下文中的变量值。比如,当闭包在函数中定义时,它可以访问函数作用域中的变量。
普通函数:普通函数不具备捕获能力,仅能访问其自身作用域内或通过参数传递的变量。
4、使用场景
闭包表达式:灵活、简洁、易于传递。用于异步操作、事件处理、数组排序等简短且不复杂的代码逻辑中。
let sortedArray = [5, 2, 3, 1].sorted { $0 < $1 } // 使用闭包进行排序
普通函数:当需要编写复杂的逻辑且函数可能在多个地方使用时,普通函数更适合。
func sortArray(array: [Int]) -> [Int] {
return array.sorted { $0 < $1 }
}
let sortedArray = sortArray(array: [5, 2, 3, 1])
为什么看起来像赋值?
因为 Swift 中,闭包本质上也是一种数据类型,所以可以像赋值其他数据类型(如 Int、String)一样进行赋值。
定义了闭包类型的变量后,你就可以把符合这个类型的闭包赋值给它。
变量后面为什么可以跟两个参数
这就是闭包的方便之处。由于 add 被赋值为一个接受两个参数的闭包,所以当你调用 add(3, 4) 时,它就像调用一个普通的函数一样,执行闭包里的代码。
在本例中,调用 add(3, 4) 相当于执行 { (a, b) in return a + b },其中 a = 3,b = 4,结果返回 7。
闭包的语法
闭包表达式的语法一般如下:
{ (参数列表) -> 返回类型 in
// 闭包的代码
}
参数列表:和函数参数类似,用来传递输入值给闭包。
返回类型:定义了闭包返回的值。
in:分隔了参数和闭包体。
闭包的特点
捕获环境变量:闭包可以捕获其定义时的上下文变量(外部变量),并且在闭包的生命周期内这些变量都可以被访问和修改。
延迟执行:闭包不会在定义时执行,而是在你明确调用时执行。你可以将闭包作为参数传递给函数或者存储在变量中,随时调用。
可以作为返回值:闭包可以从一个函数中返回,并且可以在其他地方使用。
理解闭包的嵌套函数
代码分析
前面涉及闭包的嵌套函数部分,在这里进一步进行解析:
func makeMultiplier(by multiplier: Int) -> (Int) -> Int {
func multiplierFunction(number: Int) -> Int {
return number * multiplier
}
return multiplierFunction
}
函数makeMultiplier(by:)
首先是一个makeMultiplier(by:)函数,它接受一个整数multiplier作为参数,返回值的类型是(Int) -> Int,表示返回的函数会接受一个Int并且返回一个Int。
嵌套函数multiplierFunction(number:)
里面是嵌套函数multiplierFunction(number:),它接受一个参数number,并且返回number * multiplier。
返回值
最后,makeMultiplier(by:) 返回的是 multiplierFunction,这是一个可以使用 multiplier 参数的函数。
使用过程
let multiplyByTwo = makeMultiplier(by: 2)
print(multiplyByTwo(3)) // 输出: 6
makeMultiplier(by: 2) 调用
当调用 makeMultiplier(by: 2) 时,multiplier 被设置为 2。
它返回一个嵌套函数 multiplierFunction,这个函数会将 multiplier 捕获。
现在,multiplyByTwo 变量保存了 multiplierFunction,并且记住了 multiplier 的值 2。
multiplyByTwo(3) 调用
调用 multiplyByTwo(3) 相当于调用 multiplierFunction(number: 3)。
multiplierFunction 使用了捕获的 multiplier 值(2),计算 3 * 2,最终结果为 6。
嵌套函数的特点:嵌套函数可以访问外部函数的参数和变量。在 makeMultiplier(by:) 中,multiplierFunction 就能使用外部的 multiplier 参数,这样的机制称为“捕获”。
返回函数:makeMultiplier(by:) 返回了 multiplierFunction,这个函数在调用时仍然“记得”它的 multiplier 参数。
闭包的常见用途
回调函数:在异步操作中,当操作完成时执行某个代码块。
事件处理器:处理按钮点击、手势等用户操作。
作为参数传递和返回:提高代码的灵活性和可复用性。
内存管理:捕获外部变量来维持状态信息。
总结来说,闭包是一种灵活且强大的代码块,允许你将代码的逻辑和状态保持在一起,方便传递和操作。这使得 Swift 开发中的异步操作、回调和数据处理变得更加自然和高效。
闭包是否会被自动释放
闭包是否会被自动释放,取决于是否有其他强引用指向它:
局部闭包:在局部范围定义、使用的闭包,通常不会被外部引用,因此任务完成后会自动释放。比如上面例子中的 { $0 < $1 },只在 sorted 函数中使用完毕后会被释放。
持有外部引用的闭包:如果闭包被传递到其他地方,比如被赋值给一个属性、保存在数组中,或被长期引用,那么它将不会立即释放,直到它的所有强引用都被解除。