1.前言:
循环引用
循环引用是指两个或两个以上对象互相强引用,导致所用对象无法被释放的现象。这是内存泄露的一种情况
有两个类,分别为爸爸(Father)和儿子(Son)。爸爸对儿子强引用,儿子对爸爸强引用。所以,要释放儿子必须先释放爸爸,要释放爸爸,则必须先释放儿子,如此一来,两个对象都无法被释放
在OC中,可以很简单的举出一个循环引用的例子。比如有两个类A和B,A中有一个属性是B类的实例,而B中又有一个属性是A类的实例。同时这两个属性都是strong的,这就导致了一个最简单的循环引用。
实例没有销毁,造成内存泄漏
2.swift中的循环引用
但是由于swift语法的特殊性,这样的例子不像OC中一样容易构造。因为对于一般类型的属性,Swfit要求在一个类的初始化方法中保证它一定有值。这将导致一个死循环。试想一下,A类在初始化的时候要保证它的某一个类型为B的属性先被初始化,而这个属性中又含有一个类型为A的属性需要先被初始化。
这样循环下去的后果是,没有任何一个A或者B类的对象能先被初始化。如果允许代码的话,可以编译,但是运行时会报错:“EXC_BAD_ACCESS”.
(简单的理解:当遇到了EXC_BAD_ACCESS异常,意味着访问了一个已经被释放的内存区域。)
但是Swift这个特性并不意味着,在swift里面就不会出现引用循环问题了。因为swift还提供了可选类型,这个类型可以不被赋值,默认值就是nil
与OC类似,解决循环引用问题最简单方法就是把属性定义为weak。
类中循环引用
所以可分为以下3中
2.1. 如果产生循环引用的两个属性都允许为nil,这种情况适合用弱引用来解决
随便哪一个可选类型的属性前面都可以加weak,但记住只要加一个就行了.
class ClassA {
weak var classBInstance: ClassB?
init(){
//初始化操作
}
}
当弱引用所指向的对象被回收后,这个弱引用会自动被置为nil。这一点和OC非常类似。因此也可以看到,由于nil是可选类型的特权,所以weak修饰符仅能修饰可选类型属性。
如果两个类 A 强引用 B B 弱引用A
若B至为nil 则B 不会被deinit 此时打印 B为 nil 但A中的B依然存在
若A至为nil 则A 将被deinit 此时打印 A为 nil 且B中的A也不存在
两个类中,相互引用的两个属性都为可选类型,那么可以在一个属性的前面添加weak关键字,使该变量变为弱引用.
image.png
2.2 如果产生循环引用的两个属性一个允许为nil,另一个不允许为nil,这种情况适合用无主引用来解决
只能在不能为nil的那个属性前面加unowned关键字,就是说 unowned设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil。如果尝试去调用这个引用的方法或者访问成员属性的话,程序就会崩溃.
如下错误
error: Execution was interrupted, reason: signal SIGABRT.
2.3. 如果产生循环引用的两个属性都必须有值,不能为nil,这种情况适合一个类使用无主属性,另一个类使用隐式解析可选类型
隐式解析可选类型: 类似可选类型,默认值可以设置为nil
两个属性一个在类型后面加!设置为隐式解析可选类型,另一个在属性前面加unowned关键字,设置为无主属性.
与OC不同的是,除了弱引用外,swift还提供了无主引用来打破引用循环。根据我们刚刚的讨论,导致循环引用的属性,至少有一个是可选类型。这也就是说,有可能在另一个类里面,它的属性不是可选类型:
class ClassB {
unowned var classAInstance: ClassA = ClassA()
init(){
//初始化操作
}
}
比如在B类中,classAInstance这个属性就可以不是可选类型。在这种情况下,还可以使用无主引用来打破引用循环。语法就是把weak替换为unowned关键字。unowned属性引用的对象被回收后,引用不会被置为nil,也不能被访问,否则会触发运行时错误
闭包
2.4闭包也是引用类型,怎么解决闭包的循环强引用
闭包中对任何其他元素的引用都是会被闭包自动持有的。如果我们在闭包中写了 self 这样的东西的话,那我们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:如果当前的实例直接或者间接地对这个闭包又有引用的话,就形成了一个 self -> 闭包 -> self 的循环引用
使用 [unowned self]
在闭包中定义一个捕获列表[unowned self],将self变为无主引用.这样就能够在避免产生循环强引用了
lazy var group:() -> String = { // 相当于一个没有参数返回string的函数
[unowned self] in // 定义捕获列表,将self变为无主引用
if let text = self.text { // 解包
return "\(self.name), \(text)"
}else {
return "\(self.name)"
}
}
代码链接:
https://github.com/IMKiller/swiftplayground
1.使用
2.意义
weak 和 unowned 类似,不同点是 unowned 是永远有值的。weak可以声明可选型,很多时候我们不想声明一个可选型,可选型代表着风险,此时就可将属性声明成 unowned
网友评论