闭包
闭包是用大括号括起来的,可以没有名字的函数类型的实例。闭包有三种形式:
- 全局函数:具名函数,但不捕获任何值
- 嵌套函数:在函数内部嵌套定义具名函数,可捕获包含函数中的值。
- 闭包表达式:匿名函数类型的实例,不具名代码块,可捕获上下文中的值。
闭包是引用类型,闭包变量的拷贝具有引用语义。闭包和函数实例具有同样的内存模型。
表达式:
{(参数列表1,参数列表2,...)->(返回值) in
函数体执行的内容
}
示例:
var closure = {(number1:Int,number2:Int)->Bool in
if number1>number2 {
return true
}else{
return false
}
}
print("\(closure(1,2))")
运行结果:
false
闭包对上下文的值的捕获
函数类型和闭包可以捕获其所在上下文的值。
func addhandler(step:Int) -> (()->Int) {
var sum = 0
return {
sum += step
return sum
}
}
如上例,如果该闭包函数类型的对象没有被销毁,那么该闭包对象就始终能捕获到sum变量。
//定义,未执行
let addByTen = addhandler(step: 10);
//执行并打印一次返回值
print("\(addByTen())")
print("\(addByTen())")
print("\(addByTen())")
运行结果:
10
20
30
从运行结果可以看出addByTen常量在其生存周期内,是一直持有自己闭包实例内的sum变量的,因此每次传入的step:10都可以与sum叠加。
若该闭包声明参数时的step声明为变量var,那么在闭包内也可以改变step的值,并且也被闭包捕获,每次调用闭包都会延续上一次被捕获的值的状态。
如果被捕获的值,其生存周期比闭包小,那么编译器就会将被捕获的值包装在一个临时对象里,然后在闭包对象上创建一个对象指针,指向该临时变量,一边闭包随时访问修改被捕获的值。
闭包的循环引用
class Person{
var age:Int
var name:String
lazy var printName:() -> Void = {() -> Void in
print("My name is " + self.name)
}
init(name:String,age:Int){
self.name = name
self.age = age
}
deinit{
print("销毁")
}
}
var xiaoMing:Person? = Person(name: "xiaoMing", age: 18)
xiaoMing?.printName()
//销毁xiaoMing的强引用,看会不会回收内存
xiaoMing = nil
运行结果:
My name is xiaoMing
当对象属性是一个闭包,而闭包里面又对self有引用的时候,这时就会产生循环引用,因为当self销毁的时候,闭包里面对self有引用,self的引用计数并不为0,所以self并不会被内存回收。
因此要解除这类闭包相关的循环引用,在闭包中使用self时,需要用weak关键字。
在调用了printName方法后,对象并没有被销毁,因为printName方法里的闭包对自身self有一个强引用,此时的引用计数为2,xiaoMIng = nil 之后,引用计数为1,并不为0,所以实际内存并没有被回收。
解决方式是,在闭包前,声明闭包内使用的self是无主引用self即可,如下方所示:
lazy var printName:() -> Void = {[unowned self]() -> Void in
print("My name is " + self.name)
}
运行结果:
My name is xiaoMing
销毁
或者使用弱引用:
lazy var printName:() -> Void = {[weak self]() -> Void in
print("My name is " + self!.name)
}
效果是一样的。
对于循环引用的总结
引用的双方如果在其生命周期内可能会被置为nil,那就选用弱引用。这时其对象是一个可选类型。
如果对于被创建后就没有机会置nil的情况,就选用无主引用。使用无主引用有个好处,就是在闭包内使用self的时候不再是可选类型,也就不用带“?”了,可是一旦使用了无主引用就必须确保其不会为nil,一旦出现nil 的情况,就会导致系统奔溃。