一、Block的底層結構及本質(zhì)
(1)block本質(zhì):
從代碼可以看出错英,Block的本質(zhì)就是NSObject. 也就是說block就是一個對象。
(2)block結構
利用clang將main.m文件編譯為cpp文件石抡〗两危看下block底層實現(xiàn)的結構
最終block是轉成了__main_block_impl_0的結構。
block結構體表示如下:
二熬北、Block的變量capture機制
capture機制:為了保證block內(nèi)部能夠正常訪問外部的變量疙描。
1、局部變量捕獲:(會捕獲到block內(nèi)部成為新的結構體變量成員)
(1)auto類型的直接捕獲到block內(nèi)部(值傳遞)讶隐。
證明:
再看下impl結構體的變化
可以看到起胰,age值進過構造函數(shù)直接值傳遞到impl結構體里面的age。
(2)static 變量捕獲是指針傳遞
證明:
2巫延、全局變量捕獲
(1)全局變量在block里面是直接訪問效五。(不會捕獲到block內(nèi)部產(chǎn)生新的結構體變量成員)
證明:
從截圖可以看到,age在nslog打印時(block里面的任務被包裝成函數(shù))炉峰,是直接訪問拿來用的畏妖,并沒有捕獲進到block內(nèi)部。
(3)同理疼阔,全局的obj對象戒劫,也是不會捕獲到block里面,如果是全局NSObject*obj對象就直接訪問NSObject*obj婆廊,如果是static NSObject*obj對象迅细,就是會把NSObject**obj作為傳遞進去使用(&obj傳進去)。(二級指針)
三淘邻、Block的類型
Block類型有:__NSGlobalBlock__茵典、__NSStackBlock__、__NSMallocBlock__最終都是繼承自NSBlock類型宾舅,基類是NSObject统阿。(可查看最上面的截圖)
1枚尼、__NSGlobalBlock__(存放在數(shù)據(jù)區(qū)):沒有訪問auto變量的block就是__NSGlobalBlock__;
2砂吞、__NSStackBlock__(存放在棧段):訪問了auto變量的block就是__NSStackBlock__;
3崎溃、__NSMallocBlock__(存放堆段蜻直,需要程序猿管理):__NSStackBlock__調(diào)用copy后得到block就是__NSMallocBlock__;
(1)ARC環(huán)境下證明
(2)MRC環(huán)境下證明
如果block2賦值的時候加上copy袁串,則block會變成mallocBlock概而。
說明:
(1)ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復制到堆上囱修。比如:
? ? ? ? 1赎瑰、block作為函數(shù)返回值時
? ? ? ? 2、將block賦值給__strong指針時
? ? ? ? 3破镰、block作為方法名含有usingBlock的方法參數(shù)時
? ? ? ? 4餐曼、block作為GCD API的方法參數(shù)時
(2)ARC下block屬性的建議寫法:
? ? ? ? 1、@property(strong, nonatomic) void(^block)(void);
? ? ? ? 2鲜漩、@property(copy, nonatomic) void(^block)(void);
(3)MRC下block屬性的建議寫法:@property(copy, nonatomic) void(^block)(void);
四源譬、block內(nèi)部有對象類型的auto變量,內(nèi)存管理注意點
1孕似、當block內(nèi)部訪問了對象類型的auto變量時:
先看底層結構:
main函數(shù)代碼如下
轉cpp代碼:
結構體desc里面多了兩個函數(shù)指針
copy與dispose函數(shù)的作用:對block捕獲的auto對象變量做內(nèi)存管理踩娘,retain/release等操作,具體如果操作根據(jù)auto對象變量修飾符的方式進行喉祭。
copy及dispose函數(shù)具體實現(xiàn)如下:
(1)如果block是在棧上养渴,將不會對auto變量產(chǎn)生強引用(or retain)
MRC證明:
ARC證明:
? (2)? 如果block被拷貝到堆上
1、會調(diào)用block內(nèi)部的copy函數(shù)
2泛烙、copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
3理卑、_Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong、__weak胶惰、__unsafe_unretained)做出相應的操作傻工,形成強引用(retain)或者弱引用
4、如果block從堆上移除會調(diào)用block內(nèi)部的dispose函數(shù)孵滞,dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)中捆,_Block_object_dispose函數(shù)會自動釋放引用的auto變量(release)
說明:block捕獲__weak與__strong的auto變量對象,學到這里就需要知道為什么_strong的會導致強引用了坊饶。(默認不修飾auto對象變量泄伪,隱藏了修飾符__strong。)
五匿级、__block修飾符
可以用于解決block內(nèi)部無法修改auto變量值的問題蟋滴。
block捕獲進去的auto變量染厅,是不允許被修改的。因為block的實現(xiàn)內(nèi)容也是被包裝成一個函數(shù)的調(diào)用津函。函數(shù)與函數(shù)直接是不能跨域訪問auto變量的,auto變量屬于對應的函數(shù)椥ち福空間內(nèi)。
block可以修改全局變量跟static變量尔苦。
那我們來看看給auto變量加上__block后涩馆,為什么就可以修改變量了。
main函數(shù)的代碼如下:
轉cpp后允坚,看下__block int age的變化
從底層本質(zhì)看出魂那,不再是一個簡單的int age變量,而是經(jīng)過編譯器轉換成了對象(結構體__Block_byref_age_0)稠项,結構體里面存放著int age這個成員(從這大概可以知道涯雅,為了改變int age的值,先包裝成一個對象展运,利用結構體指針來修改成員int age值)活逆。
然后在block內(nèi)部修改age的值,是通過age結構體指針去修改結構體成員age的值拗胜。以此達到了我們修改age值的目的划乖。
特別說明:
1、__block int abc基本數(shù)據(jù)類型格式的捕獲
(1)如果block此時是在stack挤土,屬于stackBlock琴庵,此時block里面捕獲__block int abc變量,此時結構體(__Block_byref_abc_0)是在棧中仰美,當然結構體的age成員值也是在棧中迷殿。
證明:
? 特別說明:此處打印的abc地址是__Block_byref_abc_0結構體里面的成員int abc的地址。不是__Block_byref_abc_0結構體的地址咖杂。但是也可以說明__Block_byref_abc_0結構體目前是存在棧中庆寺。
(2)如果block進過copy,生成的mallocBlock,里面捕獲__block int abc變量诉字,此時__Block_byref_abc_0結構體也會被copy在堆上懦尝。
證明如下:
小tips: 因為有個全局的block1強引用著block,也就意味著在其他函數(shù)中也可以用的這個block,所以__block int age也必須copy到堆中才行壤圃,這樣才能保證全局block1(30)調(diào)用陵霉,可以修改abc的值。如果abc還在棧中伍绳,可能早就被其他數(shù)據(jù)覆蓋或者函數(shù)執(zhí)行完后成為了垃圾數(shù)據(jù)踊挠。
(3)struct__main_block_impl_0結構體里面的copy及dispose函數(shù)管理由__block int abc生轉換成的__Block_byref_abc_0的內(nèi)存。
編譯cpp后得到底層代碼如下:
2冲杀、__block NSObject*obj對象類型的捕獲
(1)同樣生成__Block_byref_obj_1的結構體
與之前的__block int age不同的是效床,這個結構體里面多了兩個函數(shù)copy,dispose.這個兩個函數(shù)用來對結構體里面的obj對象成員做內(nèi)存管理睹酌。
3、__block的內(nèi)存管理
(1)當block在棧上時剩檀,并不會對__block變量產(chǎn)生強引用
MRC環(huán)境證明:
ARC環(huán)境證明:
(2)當block被copy到堆時,會調(diào)用block內(nèi)部的copy函數(shù)(copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)),_Block_object_assign函數(shù)會對__block auto變量形成強引用(retain)憋沿,也會將__block auto變量拷貝到堆上。
ARC證明
(3)特殊請看沪猴,block被copy到堆時卤妒,捕獲的__block obj局部auto對象MRC情況下有點特別,不會被retain
請看下面代碼
內(nèi)存管理指示圖
(4)當block從堆中移除時,會調(diào)用block內(nèi)部的dispose函數(shù),dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù),_Block_object_dispose函數(shù)會自動釋放引用的__block變量(release)
(5)__block的__forwarding指針妙用:當我們的block復制到堆中時字币,__block變量也會復制到堆中,這時在棧中的__block變量和堆中的__block變量的__forwarding指針都會指向堆中的__block變量共缕。這樣做的目的:即使你修改棧中的block內(nèi)部的__block變量洗出,也能達到修改到堆中__block變量,防止程序修改出錯图谷。
六翩活、解決循環(huán)引用
1、ARC環(huán)境
第三種利用__block打破循環(huán)示意圖
2便贵、MRC環(huán)境
六菠镇、總結
1、Block本質(zhì)是OC對象承璃,基類也是NSObject
2利耍、Block分為:__NSGlobalBlock__、__NSStackBlock__盔粹、__NSMallocBlock__三種類型
3隘梨、Block捕獲auto變量(假設int age),會在Block結構體里面生成對應成員int age
4舷嗡、Block不會對全局變量(假設int tall)捕獲,在block里面直接可以訪問全局int tall轴猎,不會被捕獲
5、Block捕獲局部static變量(假設static int height),會在Block結構體里面生成對應成員int *height(指針)
6进萄、當block在棧上時捻脖,對(對象類型的auto變量、__block變量)都不會產(chǎn)生強引用
7中鼠、當block拷貝到堆上時可婶,都會通過copy函數(shù)來處理它們
? ? (1)__block變量(假設變量名叫做a) 調(diào)用如下函數(shù)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
? ? (2)對象類型的auto變量(假設變量名叫做obj) 調(diào)用如下函數(shù)
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
8、當block從堆上移除時援雇,都會通過dispose函數(shù)來釋放它們
(1)__block變量(假設變量名叫做a) 調(diào)用如下函數(shù)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
(2)對象類型的auto變量(假設變量名叫做obj)
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
9扰肌、當__block變量被copy到堆時,會調(diào)用__block變量內(nèi)部的copy函數(shù)熊杨,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)曙旭,_Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong盗舰、__weak、__unsafe_unretained)做出相應的操作桂躏,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain钻趋,MRC時不會retain)
10、如果__block變量從堆上移除會調(diào)用__block變量內(nèi)部的dispose函數(shù)剂习,dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)蛮位,_Block_object_dispose函數(shù)會自動釋放指向的對象(release)
11、ARC解決循環(huán)引用:(1)用__weak鳞绕、__unsafe_unretained解決(2)用__block解決(必須要調(diào)用block)
12失仁、MRC解決循環(huán)引用:(1)__unsafe_unretained (2)__block