廢話不多說,老規(guī)矩恬惯,還是來到面試題:
一向拆,block的原理是什么?本質(zhì)是什么樣的酪耳?
帶著疑問亲铡,咋們一起看看block的底層到底長啥樣...
研究本質(zhì),我們常用的手段就是,就是將oc的代碼通過clang編譯成c++的代碼奖蔓,然后通過c++的代碼,看看到底都干了啥讹堤。
一吆鹤,研究block編譯后C++代碼
1. clang編譯
定一個(gè)block及實(shí)現(xiàn),通過clang編譯洲守,看看block底層的數(shù)據(jù)結(jié)構(gòu)是什么疑务??梗醇?
- (void)viewDidLoad {
[super viewDidLoad];
int age = 18;
void (^myBlock)(void) = ^{
NSLog(@"myBlock = %d", age);
};
myBlock();
}
利用clang在終端編譯,我的代碼是在ViewController.m里面寫的知允,所以編譯這個(gè).m文件就好
& xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m
2. 查看c++代碼
直接上圖
通過上圖可以得出:
- Block實(shí)現(xiàn)的原理:
1> Block實(shí)現(xiàn),Block在底層就是一個(gè)指向結(jié)構(gòu)體的指針叙谨,
2> 調(diào)用block時(shí)温鸽,根據(jù)Block對(duì)應(yīng)的指針找到相應(yīng)的函數(shù),進(jìn)而進(jìn)行調(diào)用手负,并把自己傳進(jìn)去涤垫;
- 編譯后的代碼,Block定義部分也就是=右邊的^{}轉(zhuǎn)換中成了一個(gè)函數(shù)竟终,這個(gè)函數(shù)接收三個(gè)參數(shù):
__ViewController__viewDidLoad_block_impl_0(func, desc, age);
這個(gè)函數(shù)到底是什么呢蝠猬??
是一個(gè)返回當(dāng)前結(jié)構(gòu)體類型的函數(shù)(類似于oc中的初始化函數(shù)),
最終將這些信息存儲(chǔ)在了結(jié)構(gòu)體中统捶,這個(gè)結(jié)構(gòu)體中具體有啥呢榆芦?
接著往下看...
3. block底層數(shù)據(jù)結(jié)構(gòu)分析
通過下圖我們分析一下,block底層的數(shù)據(jù)結(jié)構(gòu):
通過圖例喘鸟,可以知道匆绣,最后的isa是指向?qū)ο蟮慕Y(jié)構(gòu)體的。
所以得出結(jié)論:
結(jié)論: block的底層數(shù)據(jù)結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體迷守,本質(zhì)就是一個(gè)OC對(duì)象
那么犬绒,block的內(nèi)存布局,簡單的用下圖就可以表示(紅框圈起來的先不用管兑凿,后面的博客會(huì)講到):
其他的成員是不是都可以找到的了凯力,下面說下 FuncPtr這個(gè)函數(shù)是干什么的吧
4. Block的本質(zhì)-FuncPtr函數(shù)的調(diào)用
通過已經(jīng)存儲(chǔ)的FuncPtr地址值,找到具體的方法實(shí)現(xiàn)礼华,如圖所示:
找到的這個(gè)方法咐鹤,就是block的{}里面的所有實(shí)現(xiàn)。
咋們也可以驗(yàn)證下圣絮,這個(gè)FuncPtr到底存的是不是這個(gè)函數(shù)的地址值:
1> 聲明block底層數(shù)據(jù)結(jié)構(gòu)-結(jié)構(gòu)體
struct __block_impl {
void *isa; // 對(duì)象的isa
int Flags;
int Reserved;
void *FuncPtr; // 指向了block里面的實(shí)現(xiàn) { ... }
};
struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size; // 這個(gè)block得內(nèi)存大小
};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl; // 直接擁有這個(gè)結(jié)構(gòu)體變量
struct __ViewController__viewDidLoad_block_desc_0* Desc; // 這是一個(gè)指針祈惶,指向另一個(gè)結(jié)構(gòu)體的變量
int age;
};
運(yùn)行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
int age = 18;
void (^myBlock)(void) = ^
{
NSLog(@"myBlock = %d", age);
};
// 將myBlock賦值給一個(gè)結(jié)構(gòu)體
struct __ViewController__viewDidLoad_block_impl_0
* structBLock =
(__bridge struct __ViewController__viewDidLoad_block_impl_0 *)myBlock;
myBlock();
}
在myBlock(); 實(shí)現(xiàn)處打個(gè)斷點(diǎn),就可以找到地址值了,如下圖所示:
通過調(diào)試捧请,可以看出:block內(nèi)存中存儲(chǔ)的地址值凡涩,就是這個(gè){}里面的地址值。
注意:右側(cè)的堆棧信息是打開了匯編調(diào)試疹蛉,具體如圖所示:
二活箕,Block總結(jié)
- Block本質(zhì)是一個(gè)OC對(duì)象,它內(nèi)部也存在一個(gè)isa指針可款,底層數(shù)據(jù)結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體育韩;
- Block實(shí)現(xiàn)的原理:
1> Block實(shí)現(xiàn),Block在底層就是一個(gè)指向結(jié)構(gòu)體的指針闺鲸,
2> 調(diào)用block時(shí)筋讨,根據(jù)Block對(duì)應(yīng)的指針找到相應(yīng)的函數(shù),進(jìn)而進(jìn)行調(diào)用摸恍,并把自己傳進(jìn)去悉罕。
三,回答文章開頭的面試題
一误墓,block的原理是什么蛮粮?本質(zhì)是什么樣的?
原理:
1> Block實(shí)現(xiàn)谜慌,Block在底層就是一個(gè)指向結(jié)構(gòu)體的指針然想,
2> 調(diào)用block時(shí),根據(jù)Block對(duì)應(yīng)的指針找到相應(yīng)的函數(shù)欣范,進(jìn)而進(jìn)行調(diào)用变泄,并把自己傳進(jìn)去;
本質(zhì):
Block本質(zhì)是一個(gè)OC對(duì)象恼琼,它內(nèi)部也存在一個(gè)isa指針妨蛹,底層數(shù)據(jù)結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體。