要说明的是,【闭包】和【闭包表达式】不是一回事,你可将【闭包表达式】看作是【闭包】的一种形式。因为闭包还有其他两种形式存在:【全局函数】和【嵌套函数】。一般的,我们所说的闭包就是闭包表达式。
总结:闭包的表现形式:【闭包表达式】、【全局函数】、【嵌套函数】
那么,下面我就说说闭包表达式:
一,闭包表达式
1. 语法
{ (参数列表) -> 返回类型 in
实现
}
// 简单实现
{ 实现 }
2. 各种省略写法
// 声明一个含有闭包入参的函数
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
// 2.1 常规调用 in 前面是声明,后边是实现
exec(v1: 10, v2: 20, fn: {
(v1: Int, v2: Int) -> Int in
return v1 + v2
})
// 2.2 省略类型
exec(v1: 10, v2: 20, fn: {
(v1, v2) -> Int in
return v1 + v2
})
// 2.3 省略小括号()
exec(v1: 10, v2: 20, fn: {
v1, v2 -> Int in
return v1 + v2
})
// 2.4 省略返回值类型
exec(v1: 10, v2: 20, fn: {
v1, v2 in
return v1 + v2
})
// 2.5 省略return
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
// 2.6 使用简化参数名 (用$0 $1... 代替参数列表)
// 表示v1和v2相加并且返回
exec(v1: 10, v2: 20, fn: { $0 + $1 })
// 为什么可以直接用$0 和 $1 代表呢? 因为在函数定义时,是有明确类型的,所以系统会自动帮忙推导
二, 尾随闭包
- 如果将一个很长的闭包表达式,作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
- 尾随闭包是一个被书写在函数调用括号外面的闭包表达式
- 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的写法,那就不需要在函数后面写圆括号了
- 一般的,开发者联想出来的就是尾随闭包的写法
// 在我们敲下exec时,系统会自动联想,然后敲回车键,此时出来的就是尾随闭包的写法
exec(v1: 20, v2: 30) { (<#Int#>, <#Int#>) -> Int in
}
// 尾随闭包使用简化参数名
exec(v1: 10, v2: 30) { $0 + $1 }
// 唯一实参,并且是尾随闭包
func closure1(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
// 可以直接省略小括号()
closure1 { $0 + $1 }
// 可以忽略实参 _
closure1 { (_, _) -> Int in
20
}
【注意】尾随闭包的写法,是不是似曾相识,对了,就是我们常用的网络请求
// 如果这个表达式这样写,是不是就是咋们常见的网络请求
// 请求的实现
func request(finish: (String) -> Void ) {
finish("我是数据请求")
}
// 请求调用
request { (response) in
print(response)
}
// 当然可以直接这样简写
request {
print($0)
}
三,自动闭包
- 自动闭包关键字 @autoclosure
- 只支持 () -> T 格式的闭包
- 空合并运算符 ?? 本质就是 自动闭包
- 自动闭包的值,不一定执行
// 栗子
func getNum() -> Int {
return 20
}
// 1. 简单入参
func getFirst(v1: Int, v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirst(v1: 10, v2: 20)
getFirst(v1: -1, v2: 20)
getFirst(v1: 0, v2: -1)
// 如果 v1大于0了,其实就没必要执行getNum() 了,但是实际上getFirst都会执行一遍
getFirst(v1: 20, v2: getNum())
// 2. 改造下 如何实现:当v1大于0了,就不会执行v2的代码了
// 将入参改成闭包
func getFirst2(v1: Int, v2: () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
getFirst2(v1: -10, v2: {
print("--------")
return 20
})
// 尾随闭包
getFirst2(v1: 10) { () -> Int in
print("--------")
return 20
}
// 3. 再次改造,加入 @autoclosure 关键字
// @autoclosure 在声明时加上这个标志,可以直接传入int了
func getFirst2(v1: Int, v2: @autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
// 下面的写法不会报错
getFirst2(v1: 20, v2: 30)
// 下面这个不是简写
getFirst2(v1: 20, v2: { () -> Int in
return 20
}())
四,逃逸闭包
- 当一个闭包传入函数中时,在这个函数返回后才去执行闭包,那么我们就说这个闭包是逃逸的
- 简单讲,我们常见的网络请求就是【逃逸闭包】
func reqestHomeData(finish: @escaping (String) -> Void) {
DispatchQueue.global().async {
// 模拟数据加载
// 数据加载完后,回调到主线程
DispatchQueue.main.async {
finish("我是异步请求数据")
}
}
}
func reqestHomeDataNoEscaping(finish: (String) -> Void) {
finish("我是同步请求数据")
}
var number = 10
request { (response) in
// 这里面需用到 number , 就必须用到 self,显示调用
self.number = 20
print(response)
}
reqestHomeDataNoEscaping { (response) in
// 在非逃逸闭包里面,不需要self,隐式调用
number = 20
print(response)
}
五,闭包捕获值
- 一个函数和他所捕获的变量或者常量环境组合起来,成为闭包
- 闭包就是定义在函数内部的函数
- 闭包捕获的是外层函数的局部变量或常量
- 可以将闭包想象成类的实例对象
func cfn() -> () -> () {
var a = 10
// cfn1 就是闭包,函数里面的函数
func cfn1() {
// 函数里面用到了函数外层用到的局部变量量
a = 11
}
return cfn1
}
// 举个例子
typealias cfn = (Int) -> Int
func getCfn() -> cfn {
var num = 0
// 如果这个Num是全局变量,那么也是可以用的,全局变量在全局段,全局只有一份,不会销毁
// 但是如果是局部变量,那么在调用函数结束时,num就会被释放,在栈空间
// 按理说,这个num应该被释放,但是为啥好像没被释放呢
// 其实,num确实在函数调用结束后释放掉了,那么为啥还会在plus中可以使用呢,这就是闭包的捕获
// 用函数表示一个闭包
func plus(i: Int) -> Int {
// 只在这里用到num时,申请了一块堆空间,这样就不会销毁了
// 如果没有用到,就不会开辟
// 其实本质上就是 将num move 到了堆空间(汇编知识)
num += i
return num
}
// 返回的plus 和 num 形成了 闭包
return plus
}
// 获取一个函数
// fn 的内存地址中,肯定还有一份内存是存放plus的
var fn = getCfn() // 调用一次,就申请一块堆空间
print(fn(1)) // 1
print(fn(2)) // 3
print(fn(3)) // 6
print(fn(4)) // 10
var fn = getCfn() // 调用一次,num 就申请一块堆空间
var fn1 = getCfn() // 调用一次,num 就申请一块堆空间
// 总共有两份堆空间
print(fn(1)) // 1
print(fn1(2)) // 2
print(fn(3)) // 4
print(fn1(4)) // 6
// 闭包表达式表示一个闭包
func testFn() -> cfn {
var num = 0
// 利用闭包表达式来表示函数
let fn = { (v1: Int) -> Int in
num += v1
return num
}
return fn
// 简写
// return {
// num += $0
// return num
// }
}
var testfn = testFn()
print(testfn(3))
以上就是我对闭包的理解,说实在的,我是学完语法,然后做了一段时间的项目,再回头看语法时,才稍微理解了点这个闭包,因为我们常用的就是一个尾随的逃逸闭包,其他的很少接触。没有项目经验,刚接触肯定会蒙圈,只要多写多练多思考,就好了
过年快乐
网友评论