Swift閉包循環(huán)引用

無論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)giveAnswerClosureTeacher沃饶,最后由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()
    }
}

ViewControllerTeacher是弱引用母廷,viewDidLoad方法執(zhí)行完之后就不再對Teacher進(jìn)行引用,Teacher就應(yīng)該被釋放掉了糊肤,還有Student也是。

然而運(yùn)行后卻發(fā)現(xiàn)氓鄙,TeacherStudent兩者的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之后,雖然teacherstudent還是強(qiáng)引用凿菩,但是self?.isRightself(也就是teacher)就變成弱引用了机杜,因此不再構(gòu)成循環(huán)引用

所以當(dāng)ViewControllerviewDidLoad執(zhí)行完畢后,就對teacher不再進(jìn)行引用衅谷,所以teacher的retainCount變成了0后椒拗,teacher就被釋放了,然后student也就被釋放掉了

這時候获黔,控制臺便打印出了我們想要的結(jié)果:

deinit---Teacher
deinit---Student



  • 問題二

如果把teacher中的student由全局變量蚀苛,改為局部變量,并且在student閉包中調(diào)用studentname玷氏,結(jié)果會怎樣堵未?

因?yàn)?code>student現(xiàn)在是局部變量,所以無法再在原來的askQuestion中調(diào)用student?.giveAnswer()预茄,然后通過studentgiveAnswer方法調(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

也就是說在ViewControllerViewDidLoad方法執(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父阻。

但是由于GCDasyncAfter代碼塊內(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 ??


至此裕循,就把我理解中的Swift的閉包循環(huán)引用問題分享完了,如果有理解的不對的地方涨颜,歡迎大家叫交流指正费韭,共同學(xué)習(xí)~
如果感覺有幫助就點(diǎn)個??鼓勵下吧??
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庭瑰,隨后出現(xiàn)的幾起案子星持,更是在濱河造成了極大的恐慌,老刑警劉巖弹灭,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件督暂,死亡現(xiàn)場離奇詭異,居然都是意外死亡穷吮,警方通過查閱死者的電腦和手機(jī)逻翁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捡鱼,“玉大人八回,你說我怎么就攤上這事〖菡” “怎么了缠诅?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乍迄。 經(jīng)常有香客問我管引,道長,這世上最難降的妖魔是什么闯两? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任褥伴,我火速辦了婚禮,結(jié)果婚禮上漾狼,老公的妹妹穿的比我還像新娘重慢。我一直安慰自己,他們只是感情好逊躁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布伤锚。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屯援。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天榛搔,我揣著相機(jī)與錄音琳骡,去河邊找鬼啦吧。 笑死,一個胖子當(dāng)著我的面吹牛吉懊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播假勿,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼借嗽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了转培?” 一聲冷哼從身側(cè)響起恶导,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浸须,沒想到半個月后惨寿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡删窒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年裂垦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肌索。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蕉拢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诚亚,到底是詐尸還是另有隱情晕换,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布亡电,位于F島的核電站届巩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏份乒。R本人自食惡果不足惜恕汇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望或辖。 院中可真熱鬧瘾英,春花似錦、人聲如沸颂暇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耳鸯。三九已至湿蛔,卻和暖如春膀曾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阳啥。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工添谊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人察迟。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓斩狱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扎瓶。 傳聞我的和親對象是個殘疾皇子所踊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容