作者:Mitchell
一愿险、根據(jù)需求提出問題
- 請耐心把這篇文章看完颇蜡,你對 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
方法運行結束,由于是局部變量彻磁,無論是 MitPerson 和 weakPerson 都會被釋放掉碍沐,那么這個時候在 Block 中就無法拿到正真的 person 內容了。
- 按如下方法修改代碼:
- (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 類對象內容斋陪,創(chuàng)建 person 強指針。
MitPerson*person = [[MitPerson alloc]init];
2置吓、創(chuàng)建一個弱指針 weakPerson 指向person對象內容
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
3无虚、在 person 對象的 Block 內部創(chuàng)建一個強指針來指向 person 對象,為了保證當計時器執(zhí)行代碼的時候衍锚,person 對象沒有被系統(tǒng)銷毀所以我們必須在系統(tǒng)內部進行一次強引用友题,并用 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];
});
};
- 首先需要明白一些關于 Block 的概念:
- 1置森、默認情況下斗埂,block 是放在棧里面的
- 2、一旦blcok進行了copy操作凫海,block的內存就會被放在堆里面
- 3呛凶、堆立面的block(被copy過的block)有以下現(xiàn)象
- 1> block內部如果通過外面聲明的強引用來使用,那么block內部會自動產(chǎn)生一個強引用指向所使用的對象行贪。
- 2> block內部如果通過外面聲明的弱引用來使用漾稀,那么block內部會自動產(chǎn)生一個弱引用指向所使用的對象模闲。
- 我們進行這段代碼的目的:
- 首先,我們需要在 Block 塊中調用崭捍,person 對象的方法尸折,既然是在 Block 塊中我們就應該使用弱指針來引用外部變量,以此來避免循環(huán)引用殷蛇。但是又會出現(xiàn)問題实夹,什么問題呢?就是當我計時器要執(zhí)行方法的時候粒梦,發(fā)現(xiàn)對象已經(jīng)被釋放了亮航。
- 接下來就是為了避免 person 對象在計時器執(zhí)行的時候被釋放掉:那么為什么 person 對象會被釋放掉呢?因為無論我們的person強指針還是 weakPerson 弱指針都是局部變量匀们,當執(zhí)行完ViewDidLoad 的時候缴淋,指針會被銷毀。對象只有被強指針引用的時候才不會被銷毀泄朴,而我們如果直接引用外部的強指針對象又會產(chǎn)生循環(huán)引用重抖,這個時候我們就用了一個巧妙的代碼來完成這個需求。
- 首先在 person.mitBlock 引用外部 weakPerson祖灰,并在內部創(chuàng)建一個強指針去指向 person 對象钟沛,因為在內部聲明變量,Block 是不會強引用這個對象的局扶,這也就在避免的 person.mitBlock 循環(huán)引用風險的同時讹剔,又創(chuàng)建出了一個強指針指向對象。
- 之后再用 GCD 延時器 Block 來引用相對于它來說是外部的變量 strongPerson 详民,這時延時器 Block 會默認創(chuàng)建出來一個強引用來引用 person 對象,當 person.mitBlock 作用域結束之后 strongPerson 會跟著被銷毀陌兑,內存中就僅剩下了 延時器 Block 強引用著 person 對象沈跨,2秒之后觸發(fā) test 方法,GCD Block 內部方法執(zhí)行完畢之后兔综,延時器和對象都被銷毀饿凛,這樣就完美實現(xiàn)了我們的需求。
- 最后再用一張圖來闡述各個指針软驰、Block 與對象之間的關系
黑色代表強引用涧窒,綠色代表弱引用-
總結:person.mitBlock 中創(chuàng)建 strongPerson 是為了能夠使 GCD Block 保存 person 對象,創(chuàng)建 strongPerson 時候使用 weakPerson 是為了避免 mitBlock 直接引用外部強指針變量所造成的循環(huán)引用锭亏。
-
總結:person.mitBlock 中創(chuàng)建 strongPerson 是為了能夠使 GCD Block 保存 person 對象,創(chuàng)建 strongPerson 時候使用 weakPerson 是為了避免 mitBlock 直接引用外部強指針變量所造成的循環(huán)引用锭亏。