循环强引用会导致内存泄漏
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
swift给出两种解除循环引用的方式,弱引用weak 和无主引用unowned
一、类之间互相持有会引发强引用
class Teacher{
var student:Student?
deinit {
print("Teacher被销毁了")
}
}
class Student{
var teacher:Teacher?
deinit {
print("Student被销毁了")
}
}
var tea:Teacher?
var stu:Student?
tea = Teacher()
stu = Student()
tea?.student = stu
stu?.teacher = tea
tea = nil
stu = nil
如上所示,tea、stu置为nil之后,控制台也没有输出任何信息,证明两个对象都没有被销毁,造成了内存泄漏。
解决办法如下:在任一类里边引用属性前添加weak即可
class Teacher{
var student:Student?
deinit {
print("Teacher被销毁了")
}
}
class Student{
weak var teacher:Teacher?
deinit {
print("Student被销毁了")
}
}
var tea:Teacher?
var stu:Student?
tea = Teacher()
stu = Student()
tea?.student = stu
stu?.teacher = tea
tea = nil
stu = nil
控制台:
Teacher被销毁了
Student被销毁了
其实上边提到的解决循环引用的方法,可以使用弱引用weak,也可以使用无主引用unwoned。
unowned与weak的区别是,无主引用无法在实例被销毁后被设置为nil,这时候被访问就会触发运行时错误。
倘若你使用 weak,属性可以是可选类型,即允许有 nil 值的情况。另一方面,倘若你使用 unowned,它不允许设为可选类型。因为一个 unowned 属性不能为可选类型,
根据属性是否为可选类型,你可以在 weak 和 unowned 之间进行选择。
tips:
- 当两个实例属性都允许为nil时,适合弱引用;
- 当两个实例属性一个允许为nil,另一个不允许为nil时,适合无主引用;
- 当两个实例属性都不允许为nil时,这时候为了解决两个实例之间的循环引用,就需要一个类使用无主引用,另一个类使用隐式解析可选属性,具体如下:
首先需要明确一点,可选类型使用的时候需要解包,隐式可选类型使用的时候不需要解包,详见Optional可选类型
class City{
let name:String
var province:Province!
init(cityName:String, provinceName:String){
self.name = cityName
self.province = Province(name:provinceName, city:self)
}
deinit {
print("City Destoryed")
}
}
class Province{
let name:String
unowned var city:City
init(name: String, city:City){
self.name = name
self.city = city
}
deinit {
print("Province Destoryed")
}
}
var city:City? = City(cityName:"石家庄" ,provinceName:"河北")
print(city!.province.name)
city = nil
控制台:
河北
City Destoryed
Province Destoryed
本人的疑问:
1、为什么要声明为隐式可选类型?
排除法:
- 声明为非可选类型
var province:Province
?
系统会报错,详细信息见图片,是因为self的存储属性还没有初始化完成,你就使用self当做参数,所以报错。
所以需要声明为隐式可选类型,这样意味着province有默认值nil。调用self当做参数就不会报错。
-
声明为可选类型?
如下图,既然已经确定province有值,再声明成可选类型的话就会造成代码冗余,最明显的就是空合运算符那里,根本就不会走到。
Snip20170628_5.png
二、闭包可能引起循环强引用
造成强引用的原因是闭包赋值给类的属性,同时闭包内部引用了这个类的实例,跟OC里边的block循环引用是一样一样的。
看下边例子:
class Student{
var name:String
var score:Int
//这里使用lazy是因为下边用到了self.score 不使用lazy会提示我们self还没有初始化.
//使用了懒加载就不一样了,等用到level的时候self肯定已经初始化了,就不会报错了。
lazy var level :() -> String= {
in
switch self.score{
case 0..<60:
return "D"
default:
return "E"
}
}
init (name:String, score:Int){
self.name = name
self.score = score
}
deinit {
print("Student destoryed")
}
}
var stu:Student?
stu = Student(name:"张三", score:45)
print(stu!.level())
stu = nil
控制台: 并没有输出销毁信息,证明循环引用了。
D
如何解决循环引用呢?
解决方法有三种
- [weak self]
- [unowned self]
- weak var weakSelf = self 然后使用weakSelf
class Student{
var name:String
var score:Int
//这里使用lazy是因为下边用到了self.score 不使用lazy会提示我们self还没有初始化.
//使用了懒加载就不一样了,等用到level的时候self肯定已经初始化了,就不会报错了。
lazy var level:() -> String = {
//方式一:
//[weak self] in
//switch self!.score{
//方式二:
//[unowned self] in
//switch self.score{
//方式三:
weak var weakSelf = self
switch weakSelf!.score{
case 0..<60:
return "D"
default:
return "E"
}
}
init (name:String, score:Int){
self.name = name
self.score = score
}
deinit {
print("Student destoryed")
}
}
var stu:Student?
stu = Student(name:"张三", score:45)
print(stu!.level())
stu = nil
控制台:
D
Student destoryed
证明Student
对象销毁了,解决循环引用成功
这里详细解释下方式三
其实方式三是可以用的,只不过我这里的例子有点尴尬。
weak var weakSelf = self
这句代码是一定要放到闭包外边的,因为就是为了防止引用self。你放到里边之后还不是引用了。
我这里是因为在初始化完成之前不能使用self,会报错。所以方式三的道理是没错的。
有不懂的可以随时私信我。我会解答