什么是闭包
闭包是一个捕获了上下文的常量或者变量的函数。
func test(){
print("test")
}
上面的函数是一个全局函数,也是一种特殊的闭包,只不过当前的全局函数并不捕获值。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
print("----")
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
上面的incrementer我们称之为内嵌函数,同时从上层函数makeIncrementer中捕获变量runnungTotal
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
这种就属于我们比较熟知的闭包表达式,是一个匿名函数,而且从上下文中捕获变量和常量。 其中闭包表达式是Swift语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:
- 利用上下文推断参数和返回值类型
- 单表达式可以隐式返回,即省略
return关键字 - 参数名称的简写(比如我们的$0)
- 尾随闭包表达式
闭包表达式
我们先来一起回顾一下闭包表达式的定义。
{ (param) -> ReturnType in
//方法体 to something
}
首先按照我们之前的知识积累,OC中的Block其实是一个匿名函数,所以这个表达式要具备
- 作用域(也就是大括号)
- 函数和返回值
- 函数题(in)之后的代码
Swift中的闭包既可以当做变量,也可以当做参数传递,这里我们看一下下面的例子熟悉一下:
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
同样的我们也可以把我们的闭包声明一个可选类型:
//错误的写法
var closure : (Int) -> Int?
closure = nil
//正确的写法
var closure : ((Int) -> Int)?
closure = nil
还可以通过let关键字将闭包声明为一个常量(也就意味着一旦赋值之后就不能改变了)
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
//再次赋值会报错 改成var声明就不会了
closure = {(age: Int) in
return age
}
同时也可以作为函数的参数
func test(param : () -> Int){
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age
}
尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很长,我们可以通过尾随闭包的书写方式来提高代码的可读性。
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool{
return by(a, b, c)
}
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
})
如果上面的参数再长一点,这里我们看一个函数调用是不是就非常费劲,特别是在代码量多的时候
test(10, 20, 30){(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
}
这样一眼看上去就知道一个函数调用,后面是一个闭包表达式。大家看这个写法,当前闭包表达式{}放在了函数外面 其实如下array.sorted其实就是一个尾随闭包,而且这个函数就只有一个参数。可以逐渐简化
var array = [1, 2, 3]
array.sort{(item1 : Int, item2: Int) -> Bool in return item1 < item2 }
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
array.sort(by: {(item1, item2) in return item1 < item2 })
array.sort{(item1, item2) in item1 < item2 }
array.sort{ return $0 < $1 } //self
array.sort{ $0 < $1 }
array.sort(by: <)
捕获值
这里我们借助官方文档中的例子来具体说明:
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
print(makeInc())
print(makeInc())
print(makeInc())
可以思考下,上面的print输出的都是什么?
这里每次都会在上次函数执行的基础上累加。按道理来说runningTotal是一个临时变量,每次进来的时候应该是10,这里每次却会累加,所以我们通过SIL来看一看发生了什么?
我们在
SIL的文档中来搜索一下:
这里我们也可以通过断点来看一下,确实调用了
swift_allocObject这个方法。
总结:
- 一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
- 当我们每次修改的捕获值的时候,修改的是堆区的
value值 - 当每次重新执行当前函数的时候,都会重新创建内存空间、
逃逸闭包
逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且在函数返回之后调用,我们就说明这个闭包逃逸了。当我们声明一个接受闭包作为形式参数时,你可以在形式参数前写@escaping来明确闭包是允许逃逸的。
Swift3.0之后,系统默认闭包参数是被@noescaping,这里我们可以通过SIL看出来
如果我们用@escaping修饰闭包之后,我们必须显示的闭包中使用self。我们来看第一种情况:
class LGTeacher{
var complitionHandler: ((Int)->Void)?
func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void){
var runningTotal = 0
runningTotal += amount
self.complitionHandler = handler
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("LGTeaher deinit")
}
}
var t = LGTeacher()
t.doSomething()
当前我们的complitionHandler作为当前LGTeacher是在当前方法makeIncrementer调用完成之后才会调用,这个时候闭包的生命周期是要比当前方法的生命周期长,所以我们说complitionHandler这个闭包逃逸了。
我们再接着看另外一个例子:
class LGTeacher{
var complitionHandler: ((Int)->Void)?
func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void){
var runningTotal = 0
runningTotal += amount
// self.complitionHandler = handler
DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
handler(runningTotal)
}
print("makeIncrementer")
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
print("doSomething")
}
deinit {
print("LGTeaher deinit")
}
}
var t = LGTeacher()
t.doSomething()
t.complitionHandler?(10)
这里的例子同样的,当前方法执行的过程中不会等待闭包执行完成之后再执行,而是直接返回,所以当前闭包的生命周期比方法长,这里我们要将闭包声明成逃逸闭包的方式。
自动闭包
我们先来看下面这个例子
func debugOutPrint(_ condition: Bool , _ message: String){
if condition {
print("lg_debug:\(message)")
}
}
debugOutPrint(true, "Application Error Occured")
上面代码会在当前condition为true的时候,打印我们当前的错误信息,也就意味着false的时候当前条件不会执行。 如果我们当前的字符串可能是在某个业务逻辑功能中获取的,比如瞎main这样:
func debugOutPrint(_ condition: Bool , _ message: String){
if condition {
print("lg_debug:\(message)")
}
}
func doSomething() -> String{
//do something and get error message
return "NetWork Error Occured"
}
debugOutPrint(true, doSomething())
这时候我们发现一个问题,那就是当前的condition无论是true还是false,当前的doSomething方法都会执行。如果当前的doSomething是一个耗时的任务操作,那么这里是不是就造成了一定的资源浪费。
这个时候我们想到的是把当前的参数修改成一个闭包,
func debugOutPrint(for condition: Bool , _ message: () -> String){
if condition {
print(message())
}
}
func doSomething() -> String{
//do something and get error message
print("doSomething")
return "NetWork Error Occured"
}
debugOutPrint(for: false, doSomething())
这样的活是不是就能够正常在当前条件满足的时候调用我们当前的doSomething的方法啊。同样的问题又随之而来,那就是这里是一个闭包,如果我们这个时候就是传入一个String怎么办呢?
func debugOutPrint(for condition: Bool , _ message: @autoclosure () -> String){
if condition {
print(message())
}
}
func doSomething() -> String{
//do something and get error message
print("doSomething")
return "NetWork Error Occured"
}
debugOutPrint(for: false, doSomething())
debugOutPrint(for: true, "Application Error Occured")
上面我们使用@autoclosure将当前的表达式声明成了一个自动闭包,不接收任何参数,返回值是当前内部表达式的值。所以实际上我们传入的String就是放入到一个闭包表达式中,在调用的时候返回。