
2018年09月05日
- 補(bǔ)充:轉(zhuǎn) C++ 使用的命令
注:分析參考 MJ底層原理班 內(nèi)容,本著自己學(xué)習(xí)原則記錄
本文使用的源碼為objc4-723
轉(zhuǎn) C++ 使用的命令 :
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
1 block 的定義和類(lèi)型
1.1 簡(jiǎn)單 block 定義和調(diào)用

1.2 block 的繼承鏈(以__NSGlobalBlock__類(lèi)型為參考對(duì)象)

- block的繼承鏈?zhǔn)?code>__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
- 也就說(shuō)明 block 它實(shí)際上是一個(gè) OC 對(duì)象
2 block 在 C++ 中的表現(xiàn)
2.1 將 mian.m 文件轉(zhuǎn)化為 C++ 文件
- 使用xcode工具 xcrun,指定架構(gòu)模式
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
// "-o main-arm64.cpp" 可指定輸入文件名或者省略
2.2 main.cpp文件內(nèi)容與 main.m 文件中的 mian 函數(shù)內(nèi)容直觀(guān)對(duì)比

2.2 將main.cpp 中 block 定義表達(dá)式和調(diào)用簡(jiǎn)化
- block定義
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 簡(jiǎn)化后
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
- block調(diào)用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// 簡(jiǎn)化后
block->FuncPtr(block);
2.3 block 定義簡(jiǎn)化解讀void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
-
void (*block)(void)表示 block 是一個(gè)函數(shù)指針,函數(shù)名為block -
=右邊是指,將函數(shù)__main_block_impl_0的返回值,通過(guò)地址(&)符號(hào),將函數(shù)的結(jié)果地址賦值給函數(shù)指針變量 block,但是一個(gè)是函數(shù)指針,一個(gè)結(jié)構(gòu)體,不能直接賦值,所以原定義表達(dá)式中有將結(jié)構(gòu)體地址值強(qiáng)轉(zhuǎn)為函數(shù)指針類(lèi)型的操作((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
2.4 __main_block_impl_0是一個(gè)具有同名構(gòu)造函數(shù)的結(jié)構(gòu)體

- 該結(jié)構(gòu)體內(nèi)有2個(gè)結(jié)構(gòu)體成員變量(iml、Desc),和一個(gè)與結(jié)構(gòu)體同名的構(gòu)造函數(shù),函數(shù)返回值就是該結(jié)構(gòu)體
- 所以(block定義簡(jiǎn)化表達(dá)式)
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);中調(diào)用函數(shù)__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)時(shí),就會(huì)生成一個(gè)__main_block_impl_0結(jié)構(gòu)體并返回。
2.5 __main_block_func_0 和 __main_block_desc_0_DATA

-
__main_block_func_0是 block 中封裝的代碼塊,這里指的就是{NSLog(@"Hello, World!");} -
__main_block_desc_0_DATA是一個(gè)__main_block_desc_0類(lèi)型的結(jié)構(gòu)體變量,在聲明時(shí)即進(jìn)行賦值操作,用于存放 block 的大小信息
2.6 block 調(diào)用簡(jiǎn)化解讀block->FuncPtr(block);
- 源碼
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
- 經(jīng)過(guò)上述解析,我們知道 block 此時(shí)指向的是一個(gè)
__main_block_impl_0結(jié)構(gòu)體,結(jié)構(gòu)體中的 impl結(jié)構(gòu)體成員變量?jī)?nèi)有 Funcptr 成員。
- 為什么不是通過(guò)接構(gòu)體一層層調(diào)用
block->impl->FuncPtr呢?而是直接block->FuncPtr(block);調(diào)用? - 通過(guò)前面的調(diào)用源碼
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);可知,里面調(diào)用 FuncPtr 前會(huì)進(jìn)行1層轉(zhuǎn)換,即(__block_impl *)block后,block 從類(lèi)型(void (*)()轉(zhuǎn)為__block_impl類(lèi)型,但我們知道,block 變量實(shí)際類(lèi)型是__main_block_impl_0,所以從底層上說(shuō),block 的類(lèi)型轉(zhuǎn)換實(shí)際上應(yīng)該是從__main_block_impl_0轉(zhuǎn)到__block_impl - 為甚么可以從
__main_block_impl_0轉(zhuǎn)到__block_impl呢?那時(shí)應(yīng)為結(jié)構(gòu)體的變量地址值實(shí)際等于該結(jié)構(gòu)體第一個(gè)成員變量的地址值,而__main_block_impl_0結(jié)構(gòu)體第一個(gè)成員變量就是__block_impl結(jié)構(gòu)體,所以當(dāng)要調(diào)用 FuncPtr 成員變量時(shí),就是從地址值開(kāi)始尋址,而從__main_block_impl_0開(kāi)始尋址,和從__block_impl開(kāi)始尋址,它們的起始地址值就是同一個(gè)。 -
FuncPtr指向的是函數(shù)__main_block_func_0,在調(diào)用是需要將 block 作為參數(shù)傳入。
2.7 綜上,block本質(zhì)上也是一個(gè)OC對(duì)象
- block 內(nèi)部的isa指針繼承自 NSObject,即可證明 block 即 OC 對(duì)象
- block 是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象
3 block 的帶參數(shù)及值捕獲-底層代碼展示
3.1 帶參數(shù) block

相對(duì)于無(wú)參 block 來(lái)說(shuō),變化如下
- block 的定義中,其類(lèi)型轉(zhuǎn)為
void(*)(int, int) - block 的調(diào)用中,F(xiàn)uncPtr 傳入的參數(shù)除了 block 本身外同樣多了兩個(gè)實(shí)參
3.2 auto變量的值捕獲-底層代碼展示

- block 定義時(shí),將 value 變量的值傳入值
__main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)中 -
__main_block_impl_0結(jié)構(gòu)體多了一個(gè)成員變量 value,從其構(gòu)造函數(shù)看,是用于來(lái)保存外面?zhèn)鬟M(jìn)來(lái)的 value 的值的,這種情況就是值捕獲 -
__main_block_impl_0構(gòu)造函數(shù)中的: value(_value)表示,將int _value的值賦值給到結(jié)構(gòu)體的 value 成員變量中去,這是 c++的語(yǔ)法 - 在 block 的調(diào)用時(shí),
int value = __cself->value;是指,從 block 的結(jié)構(gòu)體獲取其 value 成員變量的值,賦值給 value 變量。
3.3 static 變量的地址捕獲-底層代碼展示

- static 基本情況與 auto 差不多
- static 變量被 block 捕獲是通過(guò)地址捕獲
- 為什么 static 變量是地址捕獲,而 auto 變量是值捕獲呢?因?yàn)閍uto 變量的生命周期只有在變量聲明作用域有效,超出作用域時(shí)候就會(huì)自動(dòng)銷(xiāo)毀,所以為了保證后續(xù)block 能夠繼續(xù)使用 auto 變量,則需要將 auto 變量的值捕獲
3.4 全局變量沒(méi)有捕獲行為-底層代碼展示

- 沒(méi)有進(jìn)行捕獲行為,因?yàn)槿肿兞恳恢贝嬖趦?nèi)存中,隨處可以訪(fǎng)問(wèn)
3.5 block 對(duì) self 的捕獲
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)test;
@end
//Person.m
#import "Person.h"
@implementation Person
- (void)test {
void (^block)(void) = ^{
NSLog(@"%@",self);
};
block();
}
@end
轉(zhuǎn)為 Person.cpp后

OC方法的底層實(shí)現(xiàn)都會(huì)默認(rèn)帶上兩個(gè)參數(shù):
Person *self和SEL _cmd所以 block 中訪(fǎng)問(wèn)的 self 是 test 方法的形參 self 參數(shù),這個(gè) self 是一個(gè)局部變量,所以 block 會(huì)對(duì) self 進(jìn)行捕獲
顯然,self 不是自動(dòng)變量(auto),所以它在 block 中的訪(fǎng)問(wèn)方式是通過(guò)指針訪(fǎng)問(wèn)
-
拓展:無(wú)論上述 test 方法內(nèi)的 block 通過(guò)以下哪種方式訪(fǎng)問(wèn),都是會(huì)對(duì) self 進(jìn)行捕獲的
- (void)test { void (^block)(void) = ^{ NSLog(@"%@",_name); // 等同于 self->_name }; block(); }- (void)test { void (^block)(void) = ^{ NSLog(@"%@",[self name]); // 等同于 objc_msgSend(self, @selector("name")) }; block(); }
3.6 block 對(duì)變量的捕獲總結(jié)

4 block的類(lèi)型
4.1 block 有3中類(lèi)型
- 可以通過(guò)調(diào)用class方法或者isa指針查看具體類(lèi)型,最終都是繼承自NSBlock類(lèi)型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
-
通過(guò)定義打印 block 類(lèi)型獲取對(duì)應(yīng)的 block 類(lèi)型
4.2 不同類(lèi)型的 block 在內(nèi)存中的存放位置

4.3 不同環(huán)境(ARC、MRC)下 block 的類(lèi)型判斷
4.3.1 在 MRC 環(huán)境下(~將 Xcode 中默認(rèn)的 ARC 環(huán)境改為 MRC~)


測(cè)試代碼如下:

不同類(lèi)型的 block 進(jìn)行 copy 操作效果總結(jié)

4.3.2 在 ARC 環(huán)境下
4.3.2.1 在A(yíng)RC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況
-
block作為函數(shù)返回值時(shí)
-
將block賦值給__strong指針時(shí)
-
block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
-
block作為GCD API的方法參數(shù)時(shí)
4.3.2.2 不同環(huán)境下 block 屬性的內(nèi)存語(yǔ)義寫(xiě)法區(qū)別
MRC下block屬性的建議寫(xiě)法
@property (copy, nonatomic) void (^block)(void);ARC下block屬性的建議寫(xiě)法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
統(tǒng)一使用 copy 語(yǔ)義更直接
5 對(duì)象類(lèi)型的 auto 變量
5.1 block對(duì) 對(duì)象類(lèi)型的 auto 變量捕獲-底層代碼展示

5.2 block對(duì) 對(duì)象類(lèi)型的 static 變量捕獲-底層代碼展示

5.3 超出作用域的對(duì)象類(lèi)型的 auto 變量會(huì)自動(dòng)銷(xiāo)毀(ARC環(huán)境)

5.4 堆 block 會(huì)對(duì) 對(duì)象類(lèi)型的 auto 變量 進(jìn)行強(qiáng)引用(ARC環(huán)境)

棧 block 不會(huì)對(duì)
對(duì)象類(lèi)型的 auto 變量 進(jìn)行強(qiáng)引用
5.5 使用__weak修飾對(duì)象類(lèi)型的 auto 變量
- 讓堆 block 不再?gòu)?qiáng)引用
對(duì)象類(lèi)型的 auto 變量
6 棧block 和 堆block 對(duì)對(duì)象類(lèi)型的 auto 變量強(qiáng)弱引用總結(jié)
在使用clang轉(zhuǎn)換OC為C++代碼時(shí),可能會(huì)遇到以下問(wèn)題
cannot create __weak reference in file using manual reference
解決:通過(guò)添加運(yùn)行時(shí)參數(shù)至轉(zhuǎn)碼C++命令,獲取 ARC 下 block 對(duì)對(duì)對(duì)象類(lèi)型的 auto 變量強(qiáng)弱引用提示
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
6.1 __strong修飾的`對(duì)象類(lèi)型的 auto 變量
- 添加強(qiáng)修飾符
__strong的對(duì)象類(lèi)型的 auto 變量OC 代碼
-
通過(guò)帶運(yùn)行時(shí)信息的命令轉(zhuǎn)為 C++后
- 注意
__main_block_impl_0中捕獲的person變量,默認(rèn)都帶上__strong
6.2 __weak修飾的`對(duì)象類(lèi)型的 auto 變量

6.3 stackBlock 和 mallocBlock 的對(duì)對(duì)象類(lèi)型的 auto 變量的處理
6.3.1 對(duì)比訪(fǎng)問(wèn)基本 auto 變量和對(duì)象類(lèi)型的 auto 變量的 block 轉(zhuǎn) C++后源碼區(qū)別
重點(diǎn)區(qū)別在
struct __main_block_desc_0的結(jié)構(gòu)體成員上
-
訪(fǎng)問(wèn)
基本 auto 變量block 轉(zhuǎn) C++后源碼
-
訪(fǎng)問(wèn)
對(duì)象類(lèi)型的 auto 變量的 block 轉(zhuǎn) C++后源碼
訪(fǎng)問(wèn)對(duì)象類(lèi)型的 auto 變量的struct __main_block_desc_0的結(jié)構(gòu)體成員比訪(fǎng)問(wèn)`基本 auto 變量的多了兩個(gè)成員
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
對(duì)應(yīng)實(shí)現(xiàn)

它們的作用就是用來(lái)處理對(duì)象類(lèi)型的 auto 變量引用計(jì)數(shù)問(wèn)題
6.3.2 當(dāng)block內(nèi)部訪(fǎng)問(wèn)了對(duì)象類(lèi)型的auto變量時(shí)
如果block是在棧上(即 stackBlock),將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
如果block被拷貝到堆上(即 mallocBlock)
- 會(huì)調(diào)用block內(nèi)部的copy函數(shù)
- copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
- _Block_object_assign函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用
6.3.3 如果block從堆上移除
- 會(huì)調(diào)用block內(nèi)部的
dispose函數(shù) - dispose函數(shù)內(nèi)部會(huì)調(diào)用
_Block_object_dispose函數(shù) -
_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的auto變量(相當(dāng)于release操作)

文/Jacob_LJ(簡(jiǎn)書(shū)作者)
PS:如非特別說(shuō)明,所有文章均為原創(chuàng)作品,著作權(quán)歸作者所有,轉(zhuǎn)載需聯(lián)系作者獲得授權(quán),并注明出處,所有打賞均歸本人所有!











