無論OC中的Block還是Swift中的閉包Closure讹躯,經(jīng)常因?yàn)槭褂貌划?dāng)從而造成循環(huán)引用從而導(dǎo)致內(nèi)存泄漏菩彬,如何解閉包決循環(huán)引用問題,何時需要使用弱引用weak潮梯,又何時才該使用strong骗灶,一番研究后略有小感~
對于代理的循環(huán)引用及swift中delegate的使用請看:
Swift的代理delegate
該文章主要講解:
使用閉包如何造成的循環(huán)引用問題
怎么使用weak解決循環(huán)引用問題
閉包內(nèi)如何、何時使用strong
-
問題一
在這里我模擬一個老師Teacher
和學(xué)生Student
互動的場景秉馏,Teacher
提問askQuestion
Student
耙旦,Student
回答問題giveAnswer
,把問題的答案通過閉包回調(diào)giveAnswerClosure
給Teacher
沃饶,最后由Teacher
在回調(diào)的閉包giveAnswerClosure
中判斷答案是否正確
Student.swift文件
class Student {
var name: String?
var giveAnswerClosure: ((Int) -> Void)?
// 學(xué)生回答問題
func giveAnswer() {
// 調(diào)用閉包給出答案
giveAnswerClosure?(1)
}
deinit {
print("deinit---Student")
}
}
Teacher.swift文件
class Teacher: NSObject {
var student: Student?
var isRight: Bool? // 答案是否正確
override init() {
super.init()
student = Student()
// 閉包回調(diào)
student?.giveAnswerClosure = { answer in
// 答案是1
self.isRight = answer == 1 ? true : false
}
}
// 提問問題
func askQuestion() {
// 學(xué)生回答
student?.giveAnswer()
}
deinit {
print("deinit---Teacher")
}
}
ViewController.swift文件
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let teacher = Teacher()
teacher?.askQuestion()
}
}
ViewController
對Teacher
是弱引用母廷,viewDidLoad
方法執(zhí)行完之后就不再對Teacher
進(jìn)行引用,Teacher
就應(yīng)該被釋放掉了糊肤,還有Student
也是。
然而運(yùn)行后卻發(fā)現(xiàn)氓鄙,Teacher
和Student
兩者的deinit方法都沒走馆揉,也就是說這兩個對象都沒有被釋放掉!
原因分析:
(1)student
作為teacher
的一個屬性抖拦,也就是說teacher
是對student
進(jìn)行了強(qiáng)引用
class Teacher {
var student: Student
}
(2)因?yàn)?code>student閉包中調(diào)用了self.isRight
升酣,因此閉包持有了self
對象,使得student
的閉包強(qiáng)引用著self
态罪,這個self
也就是teacher
student?.giveAnswerClosure = { answer in
self.isRight = answer == 1 ? true : false
}
所以總結(jié)來說就是
teacher
是對student
進(jìn)行了強(qiáng)引用
噩茄,同時student
的閉包內(nèi)又對teacher
進(jìn)行強(qiáng)引用
,因此兩者之間形成了循環(huán)引用复颈,導(dǎo)致teacher
對象無法釋放绩聘,同樣student
對象也無法釋放。
解決方法
使用weak打破循環(huán)引用
// 寫法一
student?.giveAnswerClosure = { [weak self] answer in
self?.isRight = answer == 1 ? true : false
}
// 寫法二
weak var weakSelf = self
student?.giveAnswerClosure = { answer in
weakSelf?.isRight = answer == 1 ? true : false
}
使用weak之后,雖然teacher
對student
還是強(qiáng)引用凿菩,但是self?.isRight
對self
(也就是teacher
)就變成弱引用了机杜,因此不再構(gòu)成循環(huán)引用
所以當(dāng)ViewController
的viewDidLoad
執(zhí)行完畢后,就對teacher
不再進(jìn)行引用衅谷,所以teacher
的retainCount變成了0后椒拗,teacher
就被釋放了,然后student
也就被釋放掉了
這時候获黔,控制臺便打印出了我們想要的結(jié)果:
deinit---Teacher
deinit---Student
-
問題二
如果把teacher
中的student
由全局變量蚀苛,改為局部變量,并且在student
閉包中調(diào)用student
的name
玷氏,結(jié)果會怎樣堵未?
因?yàn)?code>student現(xiàn)在是局部變量,所以無法再在原來的askQuestion
中調(diào)用student?.giveAnswer()
预茄,然后通過student
的giveAnswer
方法調(diào)用giveAnswerClosure
閉包兴溜,因此改為在初始化方法中手動調(diào)用giveAnswerClosure
。
Teacher.swift文件
class Teacher: NSObject {
var isRight: Bool? // 答案是否正確
override init() {
super.init()
let student = Student()
student.name = "Tony"
// 閉包回調(diào)
student.giveAnswerClosure = { [weak self] answer in
self?.isRight = answer == 1 ? true : false
print(student.name ?? "") // 增加對student的name屬性的調(diào)用
}
// 手動調(diào)用student的閉包
student.giveAnswerClosure?(1)
}
}
結(jié)果運(yùn)行后會發(fā)現(xiàn)耻陕,控制臺打印信息為:
Tony
deinit---Teacher
也就是說在ViewController
的ViewDidLoad
方法執(zhí)行完后拙徽,teacher
對象被釋放了,但是student
對象卻沒有被釋放诗宣!
原因分析:
1)teacher為什么被釋放了膘怕?
其實(shí),雖然現(xiàn)在student
對象改為了局部變量召庞,但是它仍然是被self
強(qiáng)引用的岛心,但是由于我們weak self
了忘古,所以閉包中調(diào)用的self
是被weak
了的,teacher
并不會被student
強(qiáng)引用干旁,因此當(dāng)teacher
生命周期結(jié)束后就被釋放了争群。
2)student為什么沒有被釋放大年?
這是因?yàn)橛裎恚绻陂]包內(nèi)抹凳,使用了外面的強(qiáng)引用student
對象(也就是student.giveAnswerClosure
閉包內(nèi)使用了print(student.name ?? "")
,閉包內(nèi)會自動產(chǎn)生一個強(qiáng)引用幸冻,引用著student
對象!所以student
不會被釋放碑定!
解決方法:
跟前邊使用weak
解決self
的循環(huán)引用一樣,只需要把student weak
一下又官,讓student
在閉包中不再被強(qiáng)引用就OK了~
student.giveAnswerClosure = { [weak self, weak student] answer in
self?.isRight = answer == 1 ? true : false
print(student?.name ?? "")
}
控制臺打印信息:
Tony
deinit---Student
deinit---Teacher
-
問題三
現(xiàn)在我們在問題二的基礎(chǔ)上繼續(xù)添加一個需求外构,要求在閉包回調(diào)的2秒后再打印學(xué)生的姓名。
于是代碼修改如下:
student.giveAnswerClosure = { [weak self, weak student] answer in
self?.isRight = answer == 1 ? true : false
// 2秒后再打印學(xué)生的姓名
DispatchQueue.global().asyncAfter(deadline: .now() + 2, execute: {
print(student?.name ?? "nil")
})
}
結(jié)果執(zhí)行完后打印信息為:
deinit---Student
deinit---Teacher
nil
可以發(fā)現(xiàn)打印出來的name
是空的权烧!
原因很簡單妻率,因?yàn)?code>asyncAfter代碼塊內(nèi)的代碼是延遲2秒之后執(zhí)行的,并且不會阻塞線程伏伯,所以任務(wù)會一直往下走说搅,很快student
就被釋放了候引,所以當(dāng)2秒后打印的時候student
已經(jīng)是nil麸俘。
解決方法:使用strong
student.giveAnswerClosure = { [weak self, weak student] answer in
self?.isRight = answer == 1 ? true : false
let strongStudent = student // 使用strong
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
print(strongStudent?.name ?? "nil")
})
}
deinit---Teacher
Tony
deinit---Student
首先我們要先搞明白這幾個問題:
- 在問題二中疾掰,其閉包內(nèi)是使用的閉包外面的強(qiáng)引用
student
對象搂誉,從而導(dǎo)致閉包內(nèi)會自動產(chǎn)生一個強(qiáng)引用,引用著student
對象静檬。 - 而本問題三中的
strongStudent
是閉包內(nèi)部的強(qiáng)引用炭懊,而不是閉包外部強(qiáng)引用。 - 所以本問題三中的閉包內(nèi)聲明的強(qiáng)引用
strongStudent
不管怎么訪問都是不會干擾外部的對象拂檩,也不會自動產(chǎn)生一個強(qiáng)引用造成循環(huán)引用侮腹。
因此在2秒后的延遲執(zhí)行時,由于閉包中強(qiáng)引用strongStudent
的存在稻励,可以正常打印出name
父阻。
但是由于GCD
的asyncAfter
代碼塊內(nèi)部用到print(strongStudent?.name ?? "nil")
,用到了閉包外部的強(qiáng)引用對象strongStudent
望抽,所以asyncAfter
內(nèi)部會自動生成一個強(qiáng)引用加矛,引用著外部對象strongStudent
。
因此當(dāng)2秒后執(zhí)行完打印語句后煤篙,asyncAfter
代碼塊執(zhí)行完畢斟览,這時GCD
系統(tǒng)內(nèi)部不會再去強(qiáng)引用asyncAfter
的閉包,然后asyncAfter
也不再強(qiáng)引用strongStudent
辑奈,所以strongStudent
被釋放苛茂,至此student
對象內(nèi)所有資源被釋放完畢已烤,最終student
對象釋放。
因此在實(shí)際開發(fā)中妓羊,在會導(dǎo)致循環(huán)引用的閉包中有時(就像剛才那樣)會出現(xiàn)要使用的對象為空的情況胯究,如果我們又必須需要使用這個對象進(jìn)行一些很必要的操作時,那么對于這種情況就要果斷使用strong了躁绸。
Game Over ??