循環(huán)強引用會導致內(nèi)存泄漏
內(nèi)存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放弟塞,造成系統(tǒng)內(nèi)存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果
swift給出兩種解除循環(huán)引用的方式捐迫,弱引用weak 和無主引用unowned
一酵紫、類之間互相持有會引發(fā)強引用
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之后稳强,控制臺也沒有輸出任何信息场仲,證明兩個對象都沒有被銷毀,造成了內(nèi)存泄漏键袱。
解決辦法如下:在任一類里邊引用屬性前添加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被銷毀了
其實上邊提到的解決循環(huán)引用的方法燎窘,可以使用弱引用weak,也可以使用無主引用unwoned蹄咖。
unowned與weak的區(qū)別是褐健,無主引用無法在實例被銷毀后被設置為nil,這時候被訪問就會觸發(fā)運行時錯誤澜汤。
倘若你使用 weak,屬性可以是可選類型蚜迅,即允許有 nil 值的情況。另一方面俊抵,倘若你使用 unowned谁不,它不允許設為可選類型。因為一個 unowned 屬性不能為可選類型徽诲,
根據(jù)屬性是否為可選類型刹帕,你可以在 weak 和 unowned 之間進行選擇吵血。
tips:
- 當兩個實例屬性都允許為nil時,適合弱引用偷溺;
- 當兩個實例屬性一個允許為nil蹋辅,另一個不允許為nil時,適合無主引用挫掏;
- 當兩個實例屬性都不允許為nil時侦另,這時候為了解決兩個實例之間的循環(huán)引用,就需要一個類使用無主引用尉共,另一個類使用隱式解析可選屬性褒傅,具體如下:
首先需要明確一點,可選類型使用的時候需要解包袄友,隱式可選類型使用的時候不需要解包殿托,詳見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
碌尔?
系統(tǒng)會報錯浇辜,詳細信息見圖片券敌,是因為self的存儲屬性還沒有初始化完成,你就使用self當做參數(shù)柳洋,所以報錯待诅。
所以需要聲明為隱式可選類型,這樣意味著province有默認值nil熊镣。調(diào)用self當做參數(shù)就不會報錯卑雁。
-
聲明為可選類型?
如下圖绪囱,既然已經(jīng)確定province有值测蹲,再聲明成可選類型的話就會造成代碼冗余,最明顯的就是空合運算符那里鬼吵,根本就不會走到扣甲。
二、閉包可能引起循環(huán)強引用
造成強引用的原因是閉包賦值給類的屬性齿椅,同時閉包內(nèi)部引用了這個類的實例琉挖,跟OC里邊的block循環(huán)引用是一樣一樣的。
看下邊例子:
class Student{
var name:String
var score:Int
//這里使用lazy是因為下邊用到了self.score 不使用lazy會提示我們self還沒有初始化.
//使用了懶加載就不一樣了,等用到level的時候self肯定已經(jīng)初始化了涣脚,就不會報錯了示辈。
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
控制臺: 并沒有輸出銷毀信息,證明循環(huán)引用了遣蚀。
D
如何解決循環(huán)引用呢矾麻?
解決方法有三種
- [weak self]
- [unowned self]
- weak var weakSelf = self 然后使用weakSelf
class Student{
var name:String
var score:Int
//這里使用lazy是因為下邊用到了self.score 不使用lazy會提示我們self還沒有初始化.
//使用了懶加載就不一樣了,等用到level的時候self肯定已經(jīng)初始化了纱耻,就不會報錯了。
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
對象銷毀了险耀,解決循環(huán)引用成功
這里詳細解釋下方式三
其實方式三是可以用的膝迎,只不過我這里的例子有點尷尬。
weak var weakSelf = self
這句代碼是一定要放到閉包外邊的胰耗,因為就是為了防止引用self限次。你放到里邊之后還不是引用了。
我這里是因為在初始化完成之前不能使用self柴灯,會報錯卖漫。所以方式三的道理是沒錯的。
有不懂的可以隨時私信我赠群。我會解答