Block底層實(shí)現(xiàn)分析01-block本質(zhì)/block捕獲機(jī)制/block類(lèi)型/block訪(fǎng)問(wèn)對(duì)象auto變量引用問(wèn)題

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)化

  1. 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);
  1. 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);

  1. void (*block)(void)表示 block 是一個(gè)函數(shù)指針,函數(shù)名為block
  2. =右邊是指,將函數(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)體

  1. 該結(jié)構(gòu)體內(nèi)有2個(gè)結(jié)構(gòu)體成員變量(iml、Desc),和一個(gè)與結(jié)構(gòu)體同名的構(gòu)造函數(shù),函數(shù)返回值就是該結(jié)構(gòu)體
  2. 所以(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

  1. __main_block_func_0 是 block 中封裝的代碼塊,這里指的就是{NSLog(@"Hello, World!");}
  2. __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);
  1. 經(jīng)過(guò)上述解析,我們知道 block 此時(shí)指向的是一個(gè)__main_block_impl_0結(jié)構(gòu)體,結(jié)構(gòu)體中的 impl結(jié)構(gòu)體成員變量?jī)?nèi)有 Funcptr 成員。
  2. 為什么不是通過(guò)接構(gòu)體一層層調(diào)用block->impl->FuncPtr呢?而是直接block->FuncPtr(block);調(diào)用?
  3. 通過(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
  4. 為甚么可以從__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è)。
  5. 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ō),變化如下

  1. block 的定義中,其類(lèi)型轉(zhuǎn)為void(*)(int, int)
  2. block 的調(diào)用中,F(xiàn)uncPtr 傳入的參數(shù)除了 block 本身外同樣多了兩個(gè)實(shí)參

3.2 auto變量的值捕獲-底層代碼展示

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

3.3 static 變量的地址捕獲-底層代碼展示

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

3.4 全局變量沒(méi)有捕獲行為-底層代碼展示

  1. 沒(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后


  1. OC方法的底層實(shí)現(xiàn)都會(huì)默認(rèn)帶上兩個(gè)參數(shù):Person *selfSEL _cmd

  2. 所以 block 中訪(fǎng)問(wèn)的 self 是 test 方法的形參 self 參數(shù),這個(gè) self 是一個(gè)局部變量,所以 block 會(huì)對(duì) self 進(jìn)行捕獲

  3. 顯然,self 不是自動(dòng)變量(auto),所以它在 block 中的訪(fǎng)問(wèn)方式是通過(guò)指針訪(fǎng)問(wèn)

  4. 拓展:無(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é)

摘自 MJ 底層課課件

4 block的類(lèi)型

4.1 block 有3中類(lèi)型

  1. 可以通過(guò)調(diào)用class方法或者isa指針查看具體類(lèi)型,最終都是繼承自NSBlock類(lèi)型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
  1. 通過(guò)定義打印 block 類(lèi)型獲取對(duì)應(yīng)的 block 類(lèi)型


4.2 不同類(lèi)型的 block 在內(nèi)存中的存放位置

摘自 MJ 底層課課件

4.3 不同環(huán)境(ARC、MRC)下 block 的類(lèi)型判斷

4.3.1 在 MRC 環(huán)境下(~將 Xcode 中默認(rèn)的 ARC 環(huán)境改為 MRC~)

摘自 MJ 底層課課件

測(cè)試代碼如下:


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


摘自 MJ 底層課課件

4.3.2 在 ARC 環(huán)境下

4.3.2.1 在A(yíng)RC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況

  1. block作為函數(shù)返回值時(shí)


  2. 將block賦值給__strong指針時(shí)


  3. block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)


  4. 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++后


  1. 注意__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í)

  1. 如果block是在棧上(即 stackBlock),將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用

  2. 如果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),并注明出處,所有打賞均歸本人所有!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容