什么是闭包
闭包是一个捕获了上下文常量或者变量的函数
闭包表达式
与OC
中的block
类似,这个表达式需要具备
- 作用域(也就是大括号)
- 参数和返回值
- 函数体(也就是
in
后面的代码)
var closure : (Int) -> Int = { (age: Int) in
return age
}
这个闭包的参数为Int
,返回值为Int
我们可以将这个闭包声明成一个可选类型
var clourse:((Int)->(Int))?
同时也可以作为函数的参数
func test(param:(Int)->(Int)){
let a = param(10)
print(a)
}
test { (param) -> (Int) in
return param+1
}
尾随闭包
一种书写方式,用来提高代码可读性,当闭包表达式作为函数的最后一个参数时,将当前闭包表达式的{}
放在函数外面,一般编译器会帮我们做
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){(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
}
闭包强大的推算能力
var array = [1, 2, 3]
array.sort{(item1 : Int, item2: Int) -> Bool in return item1 < item2 }
//省略参数类型 由array的类型推算
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
//省略闭包返回值,由 in 后面的函数体推算
array.sort(by: {(item1, item2) in return item1 < item2 })
//尾随闭包写法
array.sort{(item1, item2) in item1 < item2 }
//省略参数声明,由闭包内嵌参数代替
array.sort{ return $0 < $1 }
//省略 return 关键字
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())
11
12
13
Program ended with exit code: 0
runningTotal
是一个临时变量,每次进来的时候讲道理应该是10,但是这里的输出是一直累加的,原因是底层调用了swift_allocObject
,在堆上分配了一个空间,在内嵌函数使用调用完成后释放
总结
- 一个闭包能够从上下文捕获已被定义的常量和变量。即使这些变量和变量的员作用域已经不存在了,闭包仍能够在其函数体内引用和修改这些值。
- 当我们每次修改捕获值的时候,修改的是
堆区
中的值 - 当每次重新执行当前函数时候,都会为捕获值重新创建内存空间
捕获值的方式
- 自动捕获
var age = 10
let closure = {
age += 1
}
closure()
print(age)
11
Program ended with exit code: 0
- 手动捕获
var age = 10
let closure = { [age] in
print("闭包内:\(age)")
}
age = 11
closure()
print("闭包外:\(age)")
闭包内:10
闭包外:11
Program ended with exit code: 0
总结
自动捕获的参数可以修改,并且影响外部变量
手动捕获的参数本质是值类型,不可修改,跟外部变量没关系了
逃逸闭包
逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了,在闭包参数前些@escaping
来明确闭包是允许逃逸的。
Swift 3.0
之后,系统默认闭包参数是@nonescaping
逃逸闭包一般有两种实现
- 延迟调用
- 先存储,在后面进行调用
先存储,在后面进行调用举例:
class LGTeacher{
var complitionHandler: ((Int)->Void)?
func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void){
var runningTotal = 10
runningTotal += amount
self.complitionHandler = handler
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("LGTeaher deinit")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = LGTeacher()
t.doSomething()
t.complitionHandler?(10)
}
}
10
LGTeaher deinit
延迟调用举例:
class LGTeacher{
var complitionHandler: ((Int)->Void)?
func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void){
var runningTotal = 10
runningTotal += amount
// self.complitionHandler = handler
DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
handler(runningTotal)
}
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("LGTeaher deinit")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = LGTeacher()
t.doSomething()
t.complitionHandler?(10)
}
}
10
LGTeaher deinit
逃逸闭包循环引用
非逃逸闭包是不会产生循环引用的,上下文保存栈上,而不是堆上
class LGStudent{
var age:Int = 18
}
class LGTeacher{
var student = LGStudent()
var complitionHandler: (()->Void)?
func makeIncrementer(handler: @escaping () -> Void){
self.complitionHandler = handler
}
func doSomething(){
self.makeIncrementer{
print(self.student.age)
}
}
deinit {
print("LGTeaher deinit")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = LGTeacher()
t.doSomething()
t.complitionHandler?()
}
}
18
只打印了18
,没有打印LGTeaher deinit
使用[weak self] 或者 [unowned self]解决循环引用
self.makeIncrementer{ [weak self] in
print(self?.student.age)
}
self.makeIncrementer { [weak student] in
print(student?.age)
}
self.makeIncrementer { [unowned self] in
print(self.student.age)
}
self.makeIncrementer { [unowned student] in
print(student.age)
}
18
LGTeaher deinit
总结
- 在捕获变量时,由于数据会copy一份,会影响对象的引用计数,使用
weak
或者unowned
修饰,不会影响引用计数 - 使用
weak
修饰时,会将捕获的值变为可选类型
,有可能为空 - 使用
unowned
,修饰时要确保对象存在,如果对象被释放,会出现野指针 - 非逃逸闭包不会造成循环引用,比如系统的动画闭包,比如snp布局闭包等
网友评论