請耐心把這篇文章看完匹层,你對 Block 會有更深刻的了解。
這里直接用一個需求來探究循環(huán)引用的問題:如果我想在Block中延時來運行某段代碼,這里就會出現(xiàn)一個問題忽刽,看這段代碼:
- (void)viewDidLoad {
?[super viewDidLoad];
?MitPerson*person = [[MitPerson alloc]init];
?__weak MitPerson * weakPerson = person;
?person.mitBlock = ^{
? ? ?dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ?[weakPerson test];
? ? ?});
?};
?person.mitBlock();
}
直接運行這段代碼會發(fā)現(xiàn)[weakPerson test];并沒有執(zhí)行,打印一下會發(fā)現(xiàn)夺欲,weakPerson 已經(jīng)是 Nil 了跪帝,這是由于當我們的 viewDidLoad 方法運行結(jié)束,由于是局部變量些阅,無論是 MitPerson 和 weakPerson 都會被釋放掉伞剑,那么這個時候在 Block 中就無法拿到正真的 person 內(nèi)容了。
按如下方法修改代碼:
- (void)viewDidLoad {
?[super viewDidLoad];
?MitPerson*person = [[MitPerson alloc]init];
?__weak MitPerson * weakPerson = person;
?person.mitBlock = ^{
? ? ?__strong MitPerson * strongPerson = weakPerson;
? ? ?dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ?[strongPerson test];
? ? ?});
?};
?person.mitBlock();
}
這樣當2秒過后市埋,計時器依然能夠拿到想要的 person 對象黎泣。
二、深入探究原理
這里將會對每行代碼逐步進行說明
1缤谎、開辟一段控件存儲 person 類對象內(nèi)容抒倚,創(chuàng)建 person 強指針。
MitPerson*person = [[MitPerson alloc]init];
2弓千、創(chuàng)建一個弱指針 weakPerson 指向person對象內(nèi)容
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
3衡便、在 person 對象的 Block 內(nèi)部創(chuàng)建一個強指針來指向 person 對象,為了保證當計時器執(zhí)行代碼的時候洋访,person 對象沒有被系統(tǒng)銷毀所以我們必須在系統(tǒng)內(nèi)部進行一次強引用镣陕,并用 GCD 計時器引用 strongPerson,為了保留 person 對象姻政,在下面會對這里更加詳細的說明呆抑。
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
4、執(zhí)行 Block 代碼
person.mitBlock();
下面將詳細分析一下下面這段代碼:
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
首先需要明白一些關(guān)于 Block 的概念:
1汁展、默認情況下鹊碍,block 是放在棧里面的
2、一旦blcok進行了copy操作食绿,block的內(nèi)存就會被放在堆里面
3侈咕、堆立面的block(被copy過的block)有以下現(xiàn)象
1> block內(nèi)部如果通過外面聲明的強引用來使用,那么block內(nèi)部會自動產(chǎn)生一個強引用指向所使用的對象器紧。
2> block內(nèi)部如果通過外面聲明的弱引用來使用耀销,那么block內(nèi)部會自動產(chǎn)生一個弱引用指向所使用的對象。
我們進行這段代碼的目的:
首先铲汪,我們需要在 Block 塊中調(diào)用熊尉,person 對象的方法罐柳,既然是在 Block 塊中我們就應該使用弱指針來引用外部變量,以此來避免循環(huán)引用狰住。但是又會出現(xiàn)問題张吉,什么問題呢?就是當我計時器要執(zhí)行方法的時候催植,發(fā)現(xiàn)對象已經(jīng)被釋放了肮蛹。
接下來就是為了避免 person 對象在計時器執(zhí)行的時候被釋放掉:那么為什么 person 對象會被釋放掉呢?因為無論我們的person強指針還是 weakPerson 弱指針都是局部變量查邢,當執(zhí)行完ViewDidLoad 的時候蔗崎,指針會被銷毀酵幕。對象只有被強指針引用的時候才不會被銷毀扰藕,而我們?nèi)绻苯右猛獠康膹娭羔槍ο笥謺a(chǎn)生循環(huán)引用,這個時候我們就用了一個巧妙的代碼來完成這個需求芳撒。
首先在 person.mitBlock 引用外部 weakPerson邓深,并在內(nèi)部創(chuàng)建一個強指針去指向 person 對象,因為在內(nèi)部聲明變量笔刹,Block 是不會強引用這個對象的芥备,這也就在避免的 person.mitBlock 循環(huán)引用風險的同時,又創(chuàng)建出了一個強指針指向?qū)ο蟆?/p>
之后再用 GCD 延時器 Block 來引用相對于它來說是外部的變量 strongPerson 舌菜,這時延時器 Block 會默認創(chuàng)建出來一個強引用來引用 person 對象萌壳,當 person.mitBlock 作用域結(jié)束之后 strongPerson 會跟著被銷毀,內(nèi)存中就僅剩下了 延時器 Block 強引用著 person 對象日月,2秒之后觸發(fā) test 方法袱瓮,GCD Block 內(nèi)部方法執(zhí)行完畢之后,延時器和對象都被銷毀爱咬,這樣就完美實現(xiàn)了我們的需求尺借。
最后再用一張圖來闡述各個指針、Block 與對象之間的關(guān)系
黑色代表強引用精拟,綠色代表弱引用
總結(jié):person.mitBlock 中創(chuàng)建 strongPerson 是為了能夠使 GCD Block 保存 person 對象燎斩,創(chuàng)建 strongPerson 時候使用 weakPerson 是為了避免 mitBlock 直接引用外部強指針變量所造成的循環(huán)引用。
Block循環(huán)引用.png