闭包(Closures)是独立的函数代码块,能在代码中传递及使用。Swift中的闭包与C和Objective-C中的代码块及其它编程语言中的匿名函数相似。
闭包可以在上下文的范围内捕获、存储任何被定义的常量和变量引用。因这些常量和变量的封闭性,而命名为“闭包(Closures)”。Swift能够对所有你所能捕获到的引用进行内存管理。
闭包的本质是代码块,它是函数的升级版本,函数是有名称、可复用的代码块,闭包则是比函数更加灵活的匿名代码块。
1.闭包表达式-Closure Expression
闭包表达式包含参数,返回值,及函数体代码。注意参数不能有默认值。
{
(参数列表) -> 返回值类型 in
函数体代码
}
2.闭包表达式的简写
let names = ["block1","block2","block3"]
var reversedName = name.sorted(by: {(s1: String,s2: String) -> Bool in
return s1 > s2
})
// 输出 ["block1","block2","block3"]
简化:
swift可以推断参数类型和返回值类型,因此可以不写类型。 单表达式可以不用写return. swift中可以简写实际参数名字,可以用1表示。
尾随闭包:如果函数的最后一个参数是闭包,则闭包可以写在形参小括号的外面。为了增强函数的可读性。
如果闭包表达式是函数的唯一一个参数,那么小括号也可以省略了。
var reversedName = name.sorted{ > }
3.闭包-Closure定义以及值捕获
- 一个函数和他捕获的变量、常量环境组合起来,称为闭包。
- 闭包一般是定义在函数内部的函数。
- 闭包一般捕获外层函数的局部变量、常量。
typealias Sum = (Int) -> Int // 函数类型
func getSum() -> Fn {
var num = 0
// 这个内嵌的函数其实就是一个闭包,只不过这个闭包已func的形式表现,而不是闭包表达式的形式
func add(_ i: Int) -> Int {
num += i
return num
}
return add // 其实返回的就是plus地址
}
/* 此时fn占16个字节 前8个字节放的是间接的add函数地址 后8个字节放的是堆空间的地址值*/
var fn = getSum()
/*1>.执行完上面一行代码之后,即调用getFn方法之后。
2>.按理说,getSum方法执行完,方法里面的局部变量num应该从栈空间被释放.
3>.但是此时fn里面存储了就是add这个函数地址,
而此时add方法中用到的这个num会被放到堆内存(值捕获),从而延长了num的生命周期
这样num这个内存就不会在执行完getSum之后被销毁。
4>.拓展:如果num是个全局变量,那num的存放地址就在全局数据区,就不用把num放到堆空间了,
根本不需要捕获。
*/
print(fn(1)) // 输出 1
print(fn(5)) // 输出 6
/*从上面的三次print输出的结果也可以知道,fn(1),fn(2)访问的是同一块内存地址,
所以才有了累加的效果。
*/
类比总结:可以把闭包想象成一个类的实例对象
- 1>.(闭包的)内存在堆空间上。
- 2>.捕获的局部变量、常量就相当于对象的成员(存储属性),当然是在堆内存上。
- 3>.组成闭包的函数就是相当于类内部定义的方法(就是上面的add方法)。
4.非逃逸闭包、逃逸闭包 escaping
- 非逃逸闭包、逃逸闭包是当闭包作为一个实际参数传递给一个函数。
- 非逃逸闭包:闭包调用发生在函数结束之前,闭包调用在函数作用域内。
- 逃逸闭包:闭包有可能在函数结束后调用,调用逃离了函数的作用域。需要用escaping来修饰闭包是允许逃逸的。多用于网络请求。 注意:让闭包逃逸意味着必须在闭包中显式引用self.
typealias FunBlock = () -> ()
// 非逃逸闭包
func test(_ fn: FunBlock) {
// 在函数作用域里面调用了这个闭包
fn()
}
test {
print(3)
}
// 逃逸闭包
var gFn: FunBlock?
// 在函数作用域里面没有调用fn这个闭包,而是赋值给了一个变量
func test2(_ fn: @escaping FunBlock) {
gFn = fn
}
// 异步调用fn这个闭包
func test(_ fn: @escaping FunBlock) {
DispatchQueue.global().async {
fn()
}
}
5.自动闭包
- 自动闭包是就是将一个表达式自动转成闭包。
- autoclosure只支持 () -> T 格式的参数。
- autoclosure并非只支持最后一个参数。 注意:使用了autoclosure的地方,这个值会被延迟执行。
func justFun(_ condition: @autoclosure ()->Bool,
_ message: @autoclosure ()-> String) {
return condition() ? message() : "no String"
}
// 调用
justFun(number == 5,"number 等于 5")
// autoclosure 会自动将 number 等于 5 封装成{ number == 5 }
逃逸闭包和自动闭包的使用
var names = ["just","kiw","jket","lku"]
var customeFunsList: [() -> String] = [] //存放闭包的数组
// 此时闭包没有调用,需要等函数执行完调用所以要用@escaping 修饰
func customeFuns(customeFun: @autoclosure @escaping () -> String) {
customeFunsList.append(provider)
}
// 传入的值为一个String形式,由于用autoclosure修饰,会自动转为闭包类型
//此时names.count = 4
collectCustomerProviders(customeFun: customeFunsList.remove(at: 0))
//此时names.count = 4
collectCustomerProviders(customeFun: customeFunsList.remove(at: 0))
for customeFun in customeFunsList {
print(customeFun()) //此时的闭包才被执行
}
// 此时names.count = 2