block在我們項(xiàng)目中經(jīng)常被使用到,它更輕型,使用起來更簡單,代碼更集中也連貫,方便閱讀.本文將從底層的角度,圍繞以下四點(diǎn),解析block的本質(zhì),讓你更加深刻了解block.
- block的原理是怎樣的?本質(zhì)是什么?
- __block的作用是什么?有什么注意點(diǎn)?
- block的屬性修飾詞為什么是copy?使用block有哪些注意?
- block在修改NSMutableArray時(shí),需不需要添加__block?
一: block的原理是怎樣的?本質(zhì)是什么?
- block本質(zhì)上也是一個(gè)OC對象,因?yàn)樗膬?nèi)部也有個(gè)isa指針
- block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象
接下來我們將通過底層源碼來論證上訴兩點(diǎn).
首先我們寫一個(gè)簡單的block,通過clang編譯器編譯成C++代碼,查看一下block的底層機(jī)構(gòu):
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
int height = 10;
//聲明block變量
void(^myBlock)(void) = ^{
NSLog(@"age is %d height is %d",age,height);
};
//調(diào)用block
myBlock();
}
return 0;
}
通過clang編譯器執(zhí)行編譯成C++代碼:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
clang編譯器編譯完后會(huì)得到一個(gè).cpp格式的文件,這就是我們剛才轉(zhuǎn)換的.m文件的底層代碼.我們打開.cpp文件,差不多有三萬多行代碼,我們直接拖到最下面,找到block相關(guān)的代碼.為了便于理解,我把block相關(guān)的代碼分為四個(gè)部分:
- 一: block底層數(shù)據(jù)結(jié)構(gòu)
// 一: block底層數(shù)據(jù)結(jié)構(gòu)
struct __main_block_impl_0 {
struct __block_impl impl; // 1: impl 結(jié)構(gòu)體
struct __main_block_desc_0* Desc; // 2: block描述信息的結(jié)構(gòu)體
int age; //3:捕獲的外部變量
int height;
//4: 和結(jié)構(gòu)體同名的構(gòu)造函數(shù) ( C++語法 , 類似于 OC 的init方法,返回一個(gè)結(jié)構(gòu)體對象,類似于返回self)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
通過底層代碼我們可以看到,block在底層中的數(shù)據(jù)結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體有四個(gè)部分組成:
1: struct __block_impl
2: struct __main_block_desc_0
3: 捕獲的外部變量
4:和block結(jié)構(gòu)體同名的構(gòu)造函數(shù)
我們找到struct __block_impl 結(jié)構(gòu)體
:
//struct __block_impl 結(jié)構(gòu)體
struct __block_impl {
void *isa; //指向 block 的類型
int Flags;//按位表示block的附加信息
int Reserved;//保留變量
void *FuncPtr; //封裝了執(zhí)行 block 代碼塊的函數(shù)地址
};
然后我們再找到struct __main_block_desc_0 結(jié)構(gòu)體
:
static struct __main_block_desc_0 {
size_t reserved;//保留變量大小
size_t Block_size;//block所占用的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
這3個(gè)結(jié)構(gòu)體之間的關(guān)系就像下圖這樣:
我們在網(wǎng)上看到的一張很經(jīng)典的block底層結(jié)構(gòu)的圖片就是把這兩個(gè)結(jié)構(gòu)體成員列表匯總進(jìn)去得到的:(descriptor結(jié)構(gòu)體中的copy 和 dispose 我們后面會(huì)講到)
- 二: main函數(shù)
// 1: main函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
int height = 10;
//聲明block變量
void(*myBlock)(int ,int) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, height));
//調(diào)用block
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
這段底層代碼中,聲明block和調(diào)用block的部分涉及了太多的類型轉(zhuǎn)換,不便于閱讀理解,我們?nèi)サ纛愋娃D(zhuǎn)換的部分,簡化如下:
// : main函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
int height = 10;
//聲明block
void(*myBlock)(void) = &__main_block_impl_0(//調(diào)用block數(shù)據(jù)結(jié)構(gòu)中的同名的構(gòu)造函
__main_block_func_0,//封裝了block要執(zhí)行的代碼塊的函數(shù)
&__main_block_desc_0_DATA,//block描述信息的結(jié)構(gòu)體
age,//把局部變量當(dāng)做參數(shù)傳入
height
);
//調(diào)用block
myBlock->FuncPtr(myBlock);
}
return 0;
}
可以看到,我們在申明block的時(shí)候,調(diào)用block的構(gòu)造函數(shù),傳入了四個(gè)參數(shù),分別是__main_block_func_0 , __main_block_desc_0_DATA , age , height
,我們對比著它的構(gòu)造函數(shù),看看它的內(nèi)部都做了什么:
// block底層數(shù)據(jù)結(jié)構(gòu)
struct __main_block_impl_0 {
struct __block_impl impl; // 1: impl 結(jié)構(gòu)體
struct __main_block_desc_0* Desc; // 2: block描述信息的結(jié)構(gòu)體
int age;//3: 捕獲的外部變量
int height;
//4: 同名的構(gòu)造函數(shù) ( C++語法 )
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock; //isa 指向了block的類型
impl.Flags = flags;//按位表示block的一些附加信息,這里是0
impl.FuncPtr = fp; // 封裝了block要執(zhí)行的代碼塊的函數(shù)
Desc = desc; //block描述信息的結(jié)構(gòu)體
}
};
- 首先,構(gòu)造函數(shù)的內(nèi)部會(huì)給
impl
結(jié)構(gòu)體的isa
指針賦值,決定block的類型; - 然后,再把傳進(jìn)來的
__main_block_func_0
傳給了fp
,又把fp
賦值給了impl
結(jié)構(gòu)體的FuncPtr
,__main_block_func_0
這個(gè)函數(shù)指針指向封裝了block代碼塊的函數(shù)地址; - 最后,再把傳進(jìn)來的
__main_block_desc_0_DATA
結(jié)構(gòu)體賦值給了Desc
;
其實(shí)這里還隱藏了一個(gè)很重要的一步,只是底層代碼上沒有體現(xiàn)出來:在構(gòu)造函數(shù)的內(nèi)部,會(huì)自動(dòng)把傳遞進(jìn)來_age
,_height
自動(dòng)賦值給我們block內(nèi)部的age
,height
,這就是block的捕獲機(jī)制.
我們來分別看一下__main_block_func_0
,__main_block_desc_0_DATA
這兩個(gè)結(jié)構(gòu)體長什么樣子:
- __main_block_func_0 結(jié)構(gòu)體: block 真正要執(zhí)行的代碼塊
// 封裝了block要執(zhí)行的代碼塊的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_b680e8_mi_0,a,b);
}
可以看到,就是我們.m中寫的很簡單的一句NSLog
語句.說明block的代碼塊確實(shí)是封裝成了一個(gè)函數(shù)去執(zhí)行的.
- __main_block_desc_0_DATA:
static struct __main_block_desc_0 {
size_t reserved;//保留變量的大小
size_t Block_size;//block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
這個(gè)結(jié)構(gòu)體中主要就是一個(gè)size_t Block_size
存放了block的大小.
總結(jié):
- block的底層就是一個(gè)
struct _block_impl_0
類型的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中包含一個(gè)isa
指針,所以從更深的層次來說,block就是一個(gè)OC對象. - block結(jié)構(gòu)體中又包含
impl
和Desc
結(jié)構(gòu)體,impl
結(jié)構(gòu)體中有一個(gè)非常重要的成員FuncPtr
,FuncPtr
是一個(gè)指針,指向了封裝了blcok代碼塊的函數(shù),我們看到調(diào)用block的代碼:myBlock()
的底層實(shí)際上是這樣子:myBlock->FuncPtr(myBlock);
,可以看出調(diào)用block其實(shí)就是調(diào)用了FuncPtr()這個(gè)函數(shù) -
Desc
結(jié)構(gòu)體存放了block的大小
今天就先講一下block的底層數(shù)據(jù)結(jié)構(gòu)和本質(zhì),希望小伙伴們看完后能對block有一些更深刻的認(rèn)識,如有不對,歡迎指出.互相討論,共同進(jìn)步!
下一篇,我們講解block的變量捕獲機(jī)制,探究一下block
是如何捕獲外部變量的