Block的定義
Blocks是C語言的擴(kuò)充功能跟继〉锞停可以用一句話來表示Blocks的擴(kuò)充功能:帶有自動(dòng)變量(局部變量)的匿名函數(shù)内狗。
-
Block的語法聲明:返回值類型 (^變量名)(參數(shù)列表) = ^ 返回值類型 (參數(shù)列表) 表達(dá)式
代碼表示:
void (^block)(void) = ^void (void){};
或者使用
typedef
來定義一個(gè)block:typedef void (^block)(void);
分類
在iOS中圈匆,主要有三種Block類型邻吭,NSGlobalBlock
晤斩、NSStackBlock
燎孟、NSMallocBlock
。這三種block類型主要是以block所在的儲(chǔ)存空間作為區(qū)分依據(jù)尸昧。
-
全局Block:
NSGlobalBlock
位于內(nèi)存全局區(qū)揩页,
.data
區(qū)域在Block內(nèi)部,沒有訪問外部的變量
(__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject)
-
棧Block:
NSStackBlock
位于內(nèi)存的棧存儲(chǔ)區(qū),
在Block內(nèi)部爆侣,訪問外部變量萍程,但是只讀操作,不能進(jìn)行"寫"操作
(__NSStackBlock__ : __NSStackBlock : NSBlock : NSObject)
-
堆Block:
NSMallocBlock
位于內(nèi)存的堆存儲(chǔ)區(qū)
堆Block是棧Block的復(fù)制兔仰,因此在Block內(nèi)部會(huì)訪問外部變量茫负,也是只讀操作,不能進(jìn)行"寫"操作
(__NSMallocBlock__ : __NSMallocBlock : NSBlock : NSObject)
三種類型存儲(chǔ)示意圖:
-
Block的分類簡單用下圖表示
為了方便后續(xù)的分析和展示乎赴,需要把代碼編譯成.cpp
文件忍法。編譯指令如下
模擬器:
xcrun -sdk iphonesimulator clang -rewrite-objc xxx文件名
真機(jī):
xcrun -sdk iphoneos clang -rewrite-objc xxx文件名
全局區(qū)Block(NSGlobalBlock)
-
沒有外部變量引用的block就是全局區(qū)Block,如下代碼:
//全局block NSGlobalBlock -(void)globalBlock{ void(^globalBlock)(void) = ^{ NSLog(@">>>>>globalBlock<<<<<"); }; NSLog(@"當(dāng)前的block類型為>>>>>%@",globalBlock); globalBlock(); } ------------------------------------------------------ //打印結(jié)果為: 2022-04-20 14:38:22.668397+0800 suanfaProject[5151:219121] 當(dāng)前的block類型為>>>>><__NSGlobalBlock__: 0x102b04cc0> 2022-04-20 14:38:22.668573+0800 suanfaProject[5151:219121] >>>>>globalBlock<<<<<
-
編譯后的
.cpp
文件榕吼,很復(fù)雜饿序,直截取需要的內(nèi)容:struct __block_impl { //isa指針,指向具體的Block的類羹蚣,當(dāng)前是NSGlobalBlock類型的block原探,指向?yàn)開_NSGlobalBlock__ void *isa; //表示一些block的附加信息,涉及到引用指數(shù)和銷毀的判斷顽素,會(huì)使用到該值 int Flags; //保留變量 int Reserved; // 函數(shù)指針咽弦,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址 //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù),impl內(nèi)部的FuncPtr指向這個(gè)函數(shù)的地址胁出,通過地址調(diào)用這個(gè)函數(shù)型型,就可以執(zhí)行block里面的代碼 //函數(shù)名稱類似于 __類名__調(diào)用block的方法名_block_func_標(biāo)記數(shù) :__BlockClang__globalBlock_block_func_0 void *FuncPtr; }; // @implementation BlockClang ///這個(gè)結(jié)構(gòu)體就是block的實(shí)際內(nèi)容。 struct __BlockClang__globalBlock_block_impl_0 { struct __block_impl impl; //block的描述信息全蝶, struct __BlockClang__globalBlock_block_desc_0* Desc; //結(jié)構(gòu)體的同名構(gòu)造函數(shù) //fp:是block是根據(jù)執(zhí)行的代碼而創(chuàng)建的函數(shù)的函數(shù)指針 //desc:block的描述信息結(jié)構(gòu)體 __BlockClang__globalBlock_block_impl_0(void *fp, struct __BlockClang__globalBlock_block_desc_0 *desc, int flags=0) { //在編譯階段所有類型的block的isa都指向_NSConcreteStackBlock输莺,也就是說block類型是在運(yùn)行時(shí)確定的。 impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù) static void __BlockClang__globalBlock_block_func_0(struct __BlockClang__globalBlock_block_impl_0 *__cself) { //里面是一句打印代碼 NSLog((NSString*)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_8949bb_mi_0); } //block的描述信息結(jié)構(gòu)體裸诽,并給出一個(gè)默認(rèn)值 static struct __BlockClang__globalBlock_block_desc_0 { size_t reserved; size_t Block_size; } __BlockClang__globalBlock_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__globalBlock_block_impl_0)}; //-(void)globalBlock的方法嫂用,編譯出來的文件 static void _I_BlockClang_globalBlock(BlockClang * self, SEL _cmd) { //void(*globalBlock)(void) = &__BlockClang__globalBlock_block_impl_0(__BlockClang__globalBlock_block_func_0, &__BlockClang__globalBlock_block_desc_0_DATA)); void(*globalBlock)(void) = ((void (*)())&__BlockClang__globalBlock_block_impl_0((void *)__BlockClang__globalBlock_block_func_0, &__BlockClang__globalBlock_block_desc_0_DATA)); //打印函數(shù) NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_8949bb_mi_1,globalBlock); //調(diào)用globalBlock //去掉類型強(qiáng)轉(zhuǎn)后的代碼:globalBlock->FuncPtr(globalBlock); //即調(diào)用globalBlock結(jié)構(gòu)體中FuncPtr參數(shù),傳值為block本身 ((void (*)(__block_impl *))((__block_impl *)globalBlock)->FuncPtr)((__block_impl *)globalBlock); } // @end
-
總結(jié)以上述代碼
Block的輸出為
__NSGlobalBlock__
丈冬,這也就說明了嘱函,是一種全局BlockBlock內(nèi)部沒有訪問外部的auto變量
棧Block(NSStackBlock)
-
非強(qiáng)引用Block或者非
copy
的Block,類型就是棧Block埂蕊。ARC
下需要使用__weak
修飾就是stackBlock
往弓,代碼實(shí)例如下//棧block NSStackBlock -(void)stackBlock{ int age = 10; void(^ stackBlock)(void) = ^{ NSLog(@">>>>>stackBlock<<<<<, age : %d",age); }; NSLog(@"當(dāng)前的block類型為>>>>>%@",stackBlock); stackBlock(); } ------------------------------------------------------ //打印結(jié)果為: 2022-04-20 15:49:59.063036+0800 suanfaProject[5886:260026] 當(dāng)前的block類型為>>>>><__NSStackBlock__: 0x16b5c5938> 2022-04-20 15:49:59.063267+0800 suanfaProject[5886:260026] >>>>>NSStackBlock<<<<<, age : 10
-
編譯后的
.cpp
文件,如下struct __block_impl { //isa指針蓄氧,指向具體的Block的類函似,當(dāng)前是NSGlobalBlock類型的block,指向?yàn)開_NSGlobalBlock__ void *isa; //表示一些block的附加信息喉童,涉及到引用指數(shù)和銷毀的判斷撇寞,會(huì)使用到該值 int Flags; //保留變量 int Reserved; // 函數(shù)指針,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址 //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù),impl內(nèi)部的FuncPtr指向這個(gè)函數(shù)的地址蔑担,通過地址調(diào)用這個(gè)函數(shù)牌废,就可以執(zhí)行block里面的代碼 //函數(shù)名稱類似于 __類名__調(diào)用block的方法名_block_func_標(biāo)記數(shù) :__BlockClang__globalBlock_block_func_0 void *FuncPtr; }; // @implementation BlockClang //block的結(jié)構(gòu)體 struct __BlockClang__stackBlock_block_impl_0 { struct __block_impl impl; struct __BlockClang__stackBlock_block_desc_0* Desc; //這里多了一個(gè)age的參數(shù),是和全局Block的文件不同的地方 int age; //結(jié)構(gòu)體的同名構(gòu)造函數(shù) //fp:是block是根據(jù)執(zhí)行的代碼而創(chuàng)建的函數(shù)的函數(shù)指針 //desc:block的描述信息結(jié)構(gòu)體 //多個(gè)一個(gè)age的賦值啤握,看的出來這里是一個(gè)值的復(fù)制鸟缕,并不是指針 __BlockClang__stackBlock_block_impl_0(void *fp, struct __BlockClang__stackBlock_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //block將需要執(zhí)行的代碼創(chuàng)建一個(gè)函數(shù) static void __BlockClang__stackBlock_block_func_0(struct __BlockClang__stackBlock_block_impl_0 *__cself) { int age = __cself->age; // bound by copy //打印代碼 NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_7d0b37_mi_0,age); } //block的描述信息結(jié)構(gòu)體,并給出一個(gè)默認(rèn)值 static struct __BlockClang__stackBlock_block_desc_0 { size_t reserved; size_t Block_size; } __BlockClang__stackBlock_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__stackBlock_block_impl_0)}; //-(void)stackBlock的方法排抬,編譯出來的文件 static void _I_BlockClang_stackBlock(BlockClang * self, SEL _cmd) { //外部變量 int age = 10; // void(* stackBlock)(void) = &__BlockClang__stackBlock_block_impl_0(__BlockClang__stackBlock_block_func_0, &__BlockClang__stackBlock_block_desc_0_DATA, age)); void(* stackBlock)(void) = ((void (*)())&__BlockClang__stackBlock_block_impl_0((void *)__BlockClang__stackBlock_block_func_0, &__BlockClang__stackBlock_block_desc_0_DATA, age)); //打印函數(shù) NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_7d0b37_mi_1,stackBlock); //調(diào)用globalBlock //去掉類型強(qiáng)轉(zhuǎn)后的代碼:stackBlock->FuncPtr(stackBlock); //即調(diào)用globalBlock結(jié)構(gòu)體中FuncPtr參數(shù)懂从,傳值為block本身 ((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock); } // @end
-
給Block內(nèi)引用的外部變量
age
,進(jìn)行復(fù)制操作蹲蒲,觀察現(xiàn)象
由圖上可知番甩,在block內(nèi)部使用block外部定義的局部變量時(shí),在block內(nèi)部是只讀的悠鞍,不能對他進(jìn)行修改,如果想要修改模燥,變量前要有__block修飾咖祭,或者使用static
修飾。 -
總結(jié)以上述代碼
Block的輸出為
__NSStackBlock__
蔫骂,這也就說明了么翰,是一種棧Block和全局Block不一樣的地方,在于Block結(jié)構(gòu)體中辽旋,有了需要引用的變量浩嫌,而且在Block結(jié)構(gòu)體初始化的時(shí)候,要進(jìn)行變量的賦值
棧Block對于外部的變量补胚,是以值復(fù)制的方式進(jìn)行訪問的码耐。而且在編譯階段就已經(jīng)完成了賦值,如果外部變量更改值溶其,Block內(nèi)部的變量值是不變的
在block內(nèi)進(jìn)行寫操作骚腥,編譯會(huì)報(bào)錯(cuò),提示需要使用
__block
進(jìn)行修飾瓶逃。__block
的修飾原理束铭,在下面會(huì)有說明。
堆Block(NSMallocBlock)
- 強(qiáng)引用Block或者
copy
后的block厢绝,都是堆Block契沫,代碼如下//堆block NSMallocBlock -(void)mallocBlock1{ int age = 10; //默認(rèn)是強(qiáng)引用 void(^mallocBlock)(void) = ^{ NSLog(@">>>>>NSMallocBlock<<<<<, age : %d",age); }; NSLog(@"當(dāng)前的block類型為>>>>>%@",mallocBlock); mallocBlock(); } ------------------------------------------------------ //打印結(jié)果為: 2022-04-21 14:39:23.197179+0800 suanfaProject[3997:179623] 當(dāng)前的block類型為>>>>><__NSMallocBlock__: 0x6000011b4120> 2022-04-21 14:39:23.197343+0800 suanfaProject[3997:179623] >>>>>NSMallocBlock<<<<<, age : 10
- NSMallocBlock 類型的 block 通常不會(huì)在源碼中直接出現(xiàn)昔汉,因?yàn)槟J(rèn)它是當(dāng)一個(gè) block 被 copy 的時(shí)候懈万,才會(huì)將這個(gè) block 復(fù)制到堆中。以下是一個(gè) block 被 copy 時(shí)的示例代碼 (來自 這里),可以看到钞速,在圖中框起來的代碼贷掖,目標(biāo)的 block 類型被修改為_NSConcreteMallocBlock。
Block的本質(zhì)
-
前面說過了Block的分類渴语,并且編譯出了各個(gè)分類的代碼苹威,在編譯出來的代碼中,最主要的代碼就是下面的這些:
在說明堆Block類型的時(shí)候驾凶,截圖的代碼中牙甫,是有一個(gè)void *_Block_copy(const void *arg)
方法。三種類型的Block在使用的過程中调违,都會(huì)調(diào)用這個(gè)方法窟哺。其在方法的第一步就是struct Block_layout *aBlock;
,獲取到對應(yīng)的Block技肩,而Block_layout
結(jié)構(gòu)體就是Block的數(shù)據(jù)結(jié)構(gòu)且轨。Block_layout
結(jié)構(gòu)體展示如下:#define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; #define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; BlockDisposeFunction dispose; }; #define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT }; struct Block_layout { void * __ptrauth_objc_isa_pointer isa; volatile int32_t flags; // contains ref count int32_t reserved; BlockInvokeFunction invoke; struct Block_descriptor_1 *descriptor; // imported variables };
-
在各個(gè)類型的Block打印中,可以看到各自對應(yīng)的結(jié)構(gòu)體類型
全局Block -----> NSGlobalBlock
棧Block. -----> NSStackBlock
堆Block -----> NSMallocBlock
在Block開放的源碼中虚婿,
data.m
文件中可以發(fā)現(xiàn)旋奢,上述的3種Block都是繼承自NSObject
。
-
綜上
block
底層就是一個(gè)Block_layout
類型的結(jié)構(gòu)體
然痊,這個(gè)結(jié)構(gòu)體中包含一個(gè)isa
指針至朗,本質(zhì)上是一個(gè)OC
對象block
是封裝了函數(shù)調(diào)用
以及函數(shù)調(diào)用環(huán)境
的OC
對象
Block變量截獲
-
為什么要進(jìn)行變量的捕獲?
經(jīng)過前面Block類型的說明剧浸,了解到Block的三種類型锹引,分別存放在內(nèi)存全局區(qū)、內(nèi)存棧區(qū)唆香、內(nèi)存堆區(qū)嫌变,不同的類型,銷毀的時(shí)機(jī)不同躬它。
在不同的Block內(nèi)部使用外部變量的時(shí)候初澎,需要考慮外部變量的生命周期,特別是銷毀的情況
不能出現(xiàn)在Block內(nèi)部使用的使用虑凛,外部變量已經(jīng)銷毀碑宴。
所以需要把外部變量捕獲到Block的內(nèi)部,這樣的話桑谍,使用的時(shí)候就不需要管外部的局部變量是不是還存在,只要使用內(nèi)部的捕獲變量就行
-
C語言中變量分為三類
全局變量: 作用域在全局延柠,哪個(gè)地方都能調(diào)用
-
局部變量:作用域在大括號中,只能在大括號內(nèi)調(diào)用
局部自動(dòng)變量
auto
關(guān)鍵字修飾局部靜態(tài)變量
static
關(guān)鍵字修飾
-
變量是對象的情況下锣披,也是一樣的贞间。對象類型的局部變量連所有權(quán)修飾符(__strong,__weak)一起捕獲
棧Block:不管外部變量是
強(qiáng)引用還是弱引用
贿条,block
都會(huì)弱引用
訪問對象-
堆Block:
如果外部
強(qiáng)引用
,block
內(nèi)部也是強(qiáng)引用
如果外部
弱引用
增热,block
內(nèi)部也是弱引用
-
下面展示不同變量在Block內(nèi)部的引用情況
- auto變量在Block內(nèi)部的捕獲情況
- 靜態(tài)變量在Block內(nèi)部的捕獲情況
-
全局變量在Block內(nèi)部的捕獲情況
-
由第四條得處一下結(jié)論
__block修飾符
-
前面在Block分類里說過整以,如果要在Block內(nèi)部修改引用的外部變量的值,直接修改是不允許的峻仇。編譯系統(tǒng)給的提示是需要使用
__Block
修飾符公黑。接下來就看一下__Block
的邏輯。
-
在報(bào)錯(cuò)的代碼上進(jìn)行修改摄咆,外部變量增加
__block
修飾凡蚜,并且在Block內(nèi)部修改變量的值。//堆block NSMallocBlock -(void)mallocBlock1{ //在Block需要引用的外部變量吭从,添加__Block修飾符 __block int age = 10; //默認(rèn)是強(qiáng)引用 void(^mallocBlock)(void) = ^{ //在Block內(nèi)部修改變量的值 age = 30; NSLog(@">>>>>NSMallocBlock<<<<<, age : %d",age); }; NSLog(@"當(dāng)前的block類型為>>>>>%@",mallocBlock); mallocBlock(); } ------------------------------------------------------ 打印結(jié)果為: 2022-04-22 14:26:14.442743+0800 suanfaProject[2562:85326] 當(dāng)前的block類型為>>>>><__NSMallocBlock__: 0x60000092ebe0> 2022-04-22 14:26:14.442810+0800 suanfaProject[2562:85326] >>>>>NSMallocBlock<<<<<, age : 30
可以看出朝蜘,加了
__block
后,編譯不會(huì)報(bào)錯(cuò)涩金,并且谱醇,修改變量的值成功。 -
接下來就編譯出這段代碼的
.cpp
文件進(jìn)行查看// @implementation BlockClang //編譯器將__block修飾的外部變量包裝成一個(gè)結(jié)構(gòu)體(對象)步做,在結(jié)構(gòu)體中創(chuàng)建一個(gè)同名變量, struct __Block_byref_age_0 { void *__isa; //結(jié)構(gòu)體的指針指向 __Block_byref_age_0 *__forwarding; int __flags; int __size; //同名變量 int age; }; //Block的結(jié)構(gòu)體副渴,這個(gè)結(jié)構(gòu)體就是block的實(shí)際內(nèi)容。 struct __BlockClang__mallocBlock1_block_impl_0 { struct __block_impl impl; struct __BlockClang__mallocBlock1_block_desc_0* Desc; //block內(nèi)部捕獲根據(jù)外部變量創(chuàng)建的結(jié)構(gòu)體指針 //將結(jié)構(gòu)體copy到堆上辆床,在block中使用自動(dòng)變量時(shí)佳晶,使用指針指向的結(jié)構(gòu)體中的自動(dòng)變量桅狠,于是就達(dá)到了修改外部變量的作用讼载。 __Block_byref_age_0 *age; // by ref //block結(jié)構(gòu)體的同名構(gòu)造 __BlockClang__mallocBlock1_block_impl_0(void *fp,struct __BlockClang__mallocBlock1_block_desc_0 *desc,__Block_byref_age_0 *_age,int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //-(void)mallocBlock1的方法,編譯出來的文件 static void _I_BlockClang_mallocBlock1(BlockClang * self, SEL _cmd) { //把__block修飾的變量包裝成__Block_byref_age_0結(jié)構(gòu)體中跌,并且玩層指針和變量的賦值 __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; //獲取實(shí)例化的block void(*mallocBlock)(void) = ((void (*)())&__BlockClang__mallocBlock1_block_impl_0((void *)__BlockClang__mallocBlock1_block_func_0, &__BlockClang__mallocBlock1_block_desc_0_DATA,(__Block_byref_age_0 *)&age,570425344)); //打印語句 NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_e5501c_mi_1,mallocBlock); //block的調(diào)用 ((void (*)(__block_impl *))((__block_impl *)mallocBlock)->FuncPtr)((__block_impl *)mallocBlock); } //block內(nèi)部任務(wù)創(chuàng)建出來的方法 static void __BlockClang__mallocBlock1_block_func_0(struct __BlockClang__mallocBlock1_block_impl_0 *__cself) { __Block_byref_age_0 *age = __cself->age; // bound by ref //這里是給__block修飾的外部變量的賦值操作咨堤,可以看出使用的是根據(jù)變量的指針找到內(nèi)存地址,然后進(jìn)行的賦值 (age->__forwarding->age) = 30; //打印操作 NSLog((NSString *)&__NSConstantStringImpl__var_folders_sc_3y8t0nfj5zg81hbsf6y_dj9w0000gn_T_BlockClang_e5501c_mi_0,(age->__forwarding->age)); } /* //以下代碼暫不關(guān)注 static struct __BlockClang__mallocBlock1_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __BlockClang__mallocBlock1_block_impl_0*, struct __BlockClang__mallocBlock1_block_impl_0*); void (*dispose)(struct __BlockClang__mallocBlock1_block_impl_0*); } __BlockClang__mallocBlock1_block_desc_0_DATA = { 0, sizeof(struct __BlockClang__mallocBlock1_block_impl_0), __BlockClang__mallocBlock1_block_copy_0, __BlockClang__mallocBlock1_block_dispose_0 }; static void __BlockClang__mallocBlock1_block_copy_0(struct __BlockClang__mallocBlock1_block_impl_0*dst, struct __BlockClang__mallocBlock1_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8);} static void __BlockClang__mallocBlock1_block_dispose_0(struct __BlockClang__mallocBlock1_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8);} */ // @end
-
對比一下沒有使用
__block
的編譯文件編譯器會(huì)將__block修飾的變量包裝成一個(gè)結(jié)構(gòu)體(對象)
在結(jié)構(gòu)體中新建一個(gè)同名變量
block內(nèi)部捕獲該結(jié)構(gòu)體指針
在block中使用外部變量時(shí)漩符,使用指針指向的結(jié)構(gòu)體中的外部變量
-
在對結(jié)構(gòu)體中的指針指向進(jìn)行說明
如果Block是一個(gè)棧Block(NSStackBlock)一喘,結(jié)構(gòu)體中的
__forwarding
指針指向的就是自己所在區(qū)域的地址-
如果Block是一個(gè)堆Block(NSMallocBlock),由于堆Block是棧Block復(fù)制生成的嗜暴,即從棧區(qū)復(fù)制到了堆區(qū)凸克。則在堆區(qū)會(huì)有一塊屬于Block的空間,也就會(huì)有變量結(jié)構(gòu)體的空間闷沥。這個(gè)情況下萎战,
__forwarding
指針指向的在堆區(qū)的內(nèi)存地址。- a舆逃、當(dāng)block在棧時(shí)蚂维,__Block_byref_a_0結(jié)構(gòu)體內(nèi)的__forwarding指針指向結(jié)構(gòu)體自己
- b戳粒、當(dāng)block被復(fù)制到堆中時(shí),棧中的__Block_byref_age_0結(jié)構(gòu)體也會(huì)被復(fù)制到堆中一份虫啥,而此時(shí)棧中的__Block_byref_a_0結(jié)構(gòu)體中的__forwarding指針指向的就是堆中的__Block_byref_a_0結(jié)構(gòu)體蔚约,堆中__Block_byref_a_0結(jié)構(gòu)體內(nèi)的__forwarding指針依然指向自己
-
c、通過__forwarding指針巧妙的將修改的變量賦值在堆中的__Block_byref_a_0中
block循環(huán)引用以及解決方法
循環(huán)引用的原因
-
引用計(jì)數(shù)算法(Reference Counting)涂籽,對每個(gè)對象保存一個(gè)整型的引用計(jì)數(shù)器屬性苹祟。用于記錄對象被引用的情況。
對于一個(gè)對象 A又活,只要有任何一個(gè)對象引用了 A苔咪,則 A 的引用計(jì)數(shù)器就加1;
當(dāng)引用被銷毀時(shí)柳骄,引用放發(fā)通知給對象A鹦聪,引用計(jì)數(shù)器就減1汁掠。
對象 A 的引用計(jì)數(shù)器一旦變?yōu)?,即表示對象 A 不可能再被使用,對象A被銷毀蔼夜。
-
正常釋放流程
- 當(dāng)A持有B,當(dāng)A銷毀后钮热,會(huì)給B發(fā)送信號雌隅。B收到信號后,如果此時(shí)B的引用計(jì)數(shù)為0時(shí)臼婆,則B就會(huì)銷毀抒痒,此時(shí)A,B都能正常釋放颁褂。不會(huì)引起內(nèi)存泄漏
-
循環(huán)引用
- 當(dāng)A持有B故响,B同時(shí)也持有A時(shí),此時(shí)A銷毀需要B先銷毀颁独,而B銷毀同樣需要A先銷毀彩届,就導(dǎo)致相互等待銷毀,此時(shí)A誓酒,B的引用計(jì)數(shù)都不為0樟蠕,所以A,B此時(shí)都無法釋放靠柑。 從而導(dǎo)致了內(nèi)存泄漏
解決方法
-
平常代碼里用的最多的應(yīng)該就是
__weak
修飾符寨辩。定義一個(gè)弱引用的
weakSelf
,即__weak typeof(self) weakSelf = self;
歼冰,指向self
靡狞。因?yàn)槭?code>__weak修飾,變量的引用計(jì)數(shù)不會(huì)增加
-
使用
__weak
修飾的代碼展示- (void)weakSelf{ self.name = @"jack"; __weak typeof(self) weakSelf = self; void(^block)(void) = ^{ NSLog(@"weakSelf.name>>>>>%@",weakSelf.name); }; block(); } ------------------------------------------------------ 打印結(jié)果為: 2022-04-24 10:24:20.008072+0800 suanfaProject[1716:30969] weakSelf.name>>>>>jack
-
weak-strong-dance (弱強(qiáng)共舞)
-
如果只使用
__weak
修飾的變量停巷,其引用計(jì)數(shù)不會(huì)增加耍攘。這樣的話榕栏,會(huì)出現(xiàn)block內(nèi)部持有的對象被提前釋放的情況。比如在Block內(nèi)部執(zhí)行一個(gè)計(jì)時(shí)器任務(wù)蕾各,就會(huì)出現(xiàn)定時(shí)任務(wù)在執(zhí)行的時(shí)候扒磁,退出控制器,控制器就被銷毀式曲,其變量獲取不到- (void)weakSelfShortcoming{ self.name = @"jack"; __weak typeof(self) weakSelf = self; void(^block)(void) = ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"weakSelf.name>>>>>%@",weakSelf.name); }); }; block(); } ------------------------------------------------------ 2022-04-24 10:28:11.588892+0800 suanfaProject[1768:33723] ~~~ self dealloc ~~~ //可以看出妨托,self已經(jīng)被銷毀,self的地址清空吝羞,也就獲取不到weakSelf.name 2022-04-24 10:28:11.936690+0800 suanfaProject[1768:34131] weakSelf.name>>>>>(null)
-
Block內(nèi)部嵌套定時(shí)任務(wù)兰伤,則需要同時(shí)使用
__weak
和__strong
。如果只用weak修飾
钧排,則可能出現(xiàn)block內(nèi)部持有的對象被提前釋放敦腔,為了防止block內(nèi)部變量被提前釋放,使用__strong
對引用計(jì)數(shù)+1恨溜,防止提前釋放符衔。- (void)weak_strong_Self{ self.name = @"jack"; __weak typeof(self) weakSelf = self; void(^block)(void) = ^{ //在Block內(nèi)部,使用__strong修飾 __strong typeof(weakSelf)strongSelf = weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"weakSelf.name>>>>>%@",strongSelf.name); }); }; block(); } ------------------------------------------------------ //可以看出糟袁,self是在定時(shí)任務(wù)完成以后進(jìn)行銷毀的 2022-04-24 10:35:18.498441+0800 suanfaProject[1869:38834] weakSelf.name>>>>>jack 2022-04-24 10:35:18.499136+0800 suanfaProject[1869:38688] ~~~ self dealloc ~~
-
-
__block
修飾變量- 代碼如下
- (void)blockSymbol{ self.name = @"jack"; __block BlocksVC *blockVC = self; void(^block)(void) = ^{ NSLog(@"blockVC.name>>>>>%@",blockVC.name); }; block(); } ------------------------------------------------------ 2022-04-24 10:48:00.279485+0800 suanfaProject[2049:46359] blockVC.name>>>>>jack 2022-04-24 10:48:00.625749+0800 suanfaProject[2049:46303] ~~~ self dealloc ~~~
- 代碼如下
-
把對象作為Block的參數(shù)判族,傳到Block內(nèi)部
-
代碼如下
self.name = @"jack"; void(^block)(BlocksVC *blockVC) = ^(BlocksVC *blockVC){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"blockVC.name>>>>>%@",blockVC.name); }); }; block(self); } ------------------------------------------------------ 2022-04-24 10:56:38.622366+0800 suanfaProject[2154:51224] blockVC.name>>>>>jack 2022-04-24 10:56:38.623024+0800 suanfaProject[2154:51097] ~~~ self dealloc ~~~</pre>
-