block,一個用的熟的不能再熟的東東。但是,被問起block是怎么實(shí)現(xiàn)的,為什么要使用___weak,為什么會造成循環(huán)引用,__block怎么用,block分為幾種。。。我就傻眼了。這次,就好好來分析一下,從本質(zhì)出發(fā)~
1 - Block的聲明以及實(shí)現(xiàn)
首先block的申明 (返回?cái)?shù)據(jù)類型)(^block名稱)(參數(shù));
如下代碼:
@property (copy , nonatomic) void(^blockName)(void);
// 或者如下
void(^tempHandle)(void);
block的實(shí)現(xiàn) ^返回?cái)?shù)據(jù)類型(參數(shù)){具體部分}
如下代碼:
^int(int b) {
return 1;
};
// 省略返回值和參數(shù)代碼如下
^ {
return 1;
};
2 - Block的本質(zhì)
根據(jù)之前我們學(xué)過的方法,我們先創(chuàng)建一個簡單的包含block的代碼,如下。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
bool(^gloadBlock)(int a) = ^bool(int b){
NSLog(@"--- block");
return false;
};
NSLog(@"%d",gloadBlock(1));
}
return 0;
}
使用命令指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp得到對應(yīng)的cpp文件,main.cpp。
main.cpp中包含了以下代碼
關(guān)于main函數(shù)如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_24b9e7_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
}
return 0;
}
關(guān)于__main_block_impl_0的數(shù)據(jù)結(jié)構(gòu):
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
關(guān)于__block_impl的數(shù)據(jù)結(jié)構(gòu):
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
關(guān)于__main_block_desc_0的數(shù)據(jù)結(jié)構(gòu):
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
來總結(jié)一下上面的結(jié)論。
block,就是一個結(jié)構(gòu)體指針(也是一個oc的對象),它的結(jié)構(gòu)體包含有兩個屬性impl和Desc。
impl是一個結(jié)構(gòu)體__block_impl,它包含有4個屬性,如下表格:
| 屬性名 | 內(nèi)容 |
|---|---|
| isa | 一個isa指針,關(guān)于isa指針,參照第一篇文章 |
| Flags | FIXME: 這里未完成 |
| Reserved | FIXME: 這里未完成 |
| FuncPtr | block包含函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境 |
3 - Block的變量捕獲
我們再來看下面的這個block代碼:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int intA = 123;
bool(^gloadBlock)(int a) = ^bool(int b){
NSLog(@"--- %d",intA);
return false;
};
intA = 321;
NSLog(@"%d",gloadBlock(1));
}
return 0;
}
對于這個block,輸出結(jié)果是
2021-02-08 17:20:49.357112+0800 Block_01[29634:735842] --- 123
2021-02-08 17:20:49.357532+0800 Block_01[29634:735842] 0
Program ended with exit code: 0
這個結(jié)果,也許和你想的不太一樣。為什么不是--- 321。
再比較下下面的代碼:
int intA;
int main(int argc, const char * argv[]) {
@autoreleasepool {
intA = 123;
bool(^gloadBlock)(int a) = ^bool(int b){
NSLog(@"--- %d",intA);
return false;
};
intA = 321;
NSLog(@"%d",gloadBlock(1));
}
return 0;
}
輸出結(jié)果是
2021-02-08 17:25:36.922729+0800 Block_01[29653:738966] --- 321
2021-02-08 17:25:36.923093+0800 Block_01[29653:738966] 0
Program ended with exit code: 0
對于這樣的問題。我們應(yīng)該分析下底層代碼來找尋答案。
使用命令導(dǎo)出c++的代碼分別如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int intA = 123;
bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intA));
intA = 321;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_470d12_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
}
return 0;
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
intA = 123;
bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
intA = 321;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_aec0d3_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
}
return 0;
}
注意這句代碼:
bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intA));
__main_block_impl_0構(gòu)造函數(shù)后面多了一個參數(shù)intA。
再這個結(jié)構(gòu)體__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int intA;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _intA, int flags=0) : intA(_intA) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
該結(jié)構(gòu)體中多出了 int intA這個屬性,并且構(gòu)造函數(shù)intA(_intA)可知,再創(chuàng)建這個block的時候,將在內(nèi)部創(chuàng)建一個intA屬性,將block外部intA的值傳入結(jié)構(gòu)體中。所以,在block之外改變intA的值,并不會改變block內(nèi)部的intA的值。
而將intA放入全局變量的話,block中并不會存在intA屬性。
這就是block的變量捕獲~
我們先來看一下block的變量捕獲。
| 變量類型 | 是否捕獲 | 訪問方式 |
|---|---|---|
| 局部變量(auto) | ? | 值傳遞 |
| 局部變量(static) | ? | 指針傳遞 |
| 全局變量 | ? | 直接訪問 |
我們來校驗(yàn)一下上面的結(jié)論,運(yùn)行以下代碼:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int intA = 123;
auto int intB = 123;
bool(^gloadBlock)(int a) = ^bool(int b){
NSLog(@"--- %d,%d",intA,intB);
return false;
};
intA = 321;
intB = 321;
NSLog(@"%d",gloadBlock(1));
}
return 0;
}
輸出結(jié)果:
--- 321,123
0
我們再來看一下轉(zhuǎn)換成c++的代碼吧。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int intA = 123;
auto int intB = 123;
bool(*gloadBlock)(int a) = ((bool (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &intA, intB));
intA = 321;
intB = 321;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_dfee55_mi_1,((bool (*)(__block_impl *, int))((__block_impl *)gloadBlock)->FuncPtr)((__block_impl *)gloadBlock, 1));
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *intA;
int intB;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_intA, int _intB, int flags=0) : intA(_intA), intB(_intB) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
當(dāng)局部變量是auto修飾的話(默認(rèn)為auto),block會進(jìn)行值捕獲。當(dāng)局部變量是static修飾的話,block會進(jìn)行指針捕獲。self屬于auto修飾的局部變量。
4 - Block的類型
先看一下下面的代碼:
#import <Foundation/Foundation.h>
void (^gloadBlockC)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int intA = 123;
auto int intB = 123;
// GlobalBlock
void(^globalBlock)(int a) = ^void(int b){
NSLog(@"--- %d,",b);
};
void(^globalBlockB)(void) = ^void(){
NSLog(@"--- %d,",intA);
};
gloadBlockC = ^void(){
};
NSLog(@"%@",[globalBlock class]);
NSLog(@"%@",[globalBlockB class]);
NSLog(@"%@",[gloadBlockC class]);
NSLog(@"%@",[^void(){
} class]);
// StackBlock
NSLog(@"%@",[^void(){
NSLog(@"%d",intB);
} class]);
// MallocBlock
void(^mallocBlock)(void) = ^void(){
NSLog(@"--- %d,",intB);
};
NSLog(@"%@",[mallocBlock class]);
}
return 0;
}
在ARC環(huán)境下運(yùn)行結(jié)果為:
NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSStackBlock
NSMallocBlock
在MRC環(huán)境下運(yùn)行結(jié)果為:
NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSGlobalBlock
NSStackBlock
NSStackBlock
所以,block有3種類型:global、stack、malloc。
為什么第六個block類型在ARC和MRC會不一樣?經(jīng)過測試驗(yàn)證,將__NSStackBlock__做copy后,得到的就是一個__NSMallocBlock__.所以,應(yīng)該是ARC環(huán)境下,NSStackBlock的賦值會自動加入copy操作造成的。
我們總結(jié)一下:
| block類型 | 產(chǎn)生的環(huán)境 | 存儲區(qū)域 | copy之后的效果 |
|---|---|---|---|
| GlobalBlock | block沒有訪問auto變量 | 數(shù)據(jù)區(qū)域 | 類型不變 |
| StackBlock | block訪問了auto變量 | 棧 | 從棧賦值到堆 |
| MallocBlock | StackBlock進(jìn)行了copy | 堆 | 引用計(jì)數(shù)+1 |
經(jīng)查詢資料,補(bǔ)充一個知識點(diǎn):
在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上,比如以下情況:
- block作為函數(shù)返回值時
- 將block賦值給
__strong指針時 - block作為Cocoa API中方法名含有
usingBlock的方法參數(shù)時 - block作為GCD API的方法參數(shù)時
為了自己來管理block的釋放,我們應(yīng)該將屬性的block放入堆中。所以,在ARC下,利用上述第二條特性,block用strong或者copy來修飾。而在MRC下,block就用copy來修飾。