OC底層-Block本質(1、原理)

問題

block的原理是怎樣的?本質是什麼?
複製代碼

認識

block本質上是一個oc對象,內部有一個isa指針,是封裝了函數調用以及函數調用環境的OC對象c++

探索

///簡單的block代碼
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(int ,int) = ^(int a, int b){
            NSLog(@"this is block,a = %d,b = %d",a,b);
            NSLog(@"this is block,age = %d",age);
        };
        block(3,5);
    }
    return 0;
}

複製代碼

咱們能夠使用命令行將代碼轉化爲c++查看其內部結構,與OC代碼進行比較:bash

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.miphone

而後將生成的main.cpp文件拖入工程項目中進行比對查看分析以下:

分析

在C++ 代碼中Block的定義以下:函數

void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

複製代碼

咱們能夠發現block定義中調用了__main_block_impl_0結構體的初始化函數,而且將__main_block_impl_0函數的地址賦值給了block。那麼咱們來看一下__main_block_impl_0結構體內部的結構。以下:ui

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; 
  int age; /// 咱們外層定義的int age = 10 的變量
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

複製代碼

__main_block_imp_0結構體內有一個同名構造函數__main_block_imp_0,構造函數中對一些變量進行了賦值最終會返回一個結構體。this

那麼也就是說最終將一個__main_block_imp_0結構體的地址賦值給了block變量spa

__main_block_impl_0結構體內能夠發現__main_block_impl_0構造函數中傳入了四個參數。(void *)__main_block_func_0、&__main_block_desc_0_DATA、age、flags。其中flage有默認值(C++語法,能夠默認不傳),最後的 age(_age)則表示傳入的_age參數會自動賦值給age成員,至關於age = _age。命令行

繼續:函數__main_block_func_0指針

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    int age = __cself->age; // bound by copy
    
    ///咱們在Block內部寫的打印信息
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7998ce_mi_0,a,b);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7998ce_mi_1,age);
}

複製代碼

在__main_block_func_0函數中首先取出block中age的值,緊接着能夠看到兩個熟悉的NSLog,能夠發現這兩段代碼偏偏是咱們在block塊中寫下的代碼。 那麼__main_block_func_0函數中其實存儲着咱們block中寫下的代碼。而__main_block_impl_0函數中傳入的是(void *)__main_block_func_0,也就說將咱們寫在block塊中的代碼封裝成__main_block_func_0函數,並將__main_block_func_0函數的地址傳入了__main_block_impl_0的構造函數中保存在結構體內。code

__main_block_impl_0結構體中有一個結構體變量和一個結構體指針源代碼以下:

struct __block_impl {
  void *isa; 
  int Flags;
  int Reserved;
  void *FuncPtr; ///Block塊中的代碼方法地址
  /*
  咱們能夠發現__block_impl結構體內部就有一個isa指針。所以能夠證實block本質上就是一個oc對象。而在構造函數中將函數中傳入的值分別存儲在__main_block_impl_0結構體實例中,最終將結構體的地址賦值給block。
  */
  
  
};

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)};

/*
能夠看到__main_block_desc_0中存儲着兩個參數,reserved和Block_size,而且reserved賦值爲0而Block_size則存儲着__main_block_impl_0的結構體佔用空間大小。最終將__main_block_desc_0結構體的地址傳入__main_block_func_0中賦值給Desc
*/



複製代碼

彙總

  1. __block_impl結構體中isa指針存儲着&_NSConcreteStackBlock地址,能夠暫時理解爲其類對象地址,block就是_NSConcreteStackBlock類型的。
  2. block代碼塊中的代碼被封裝成__main_block_func_0函數,FuncPtr則存儲着__main_block_func_0函數的地址。
  3. Desc指向__main_block_desc_0結構體對象,其中存儲__main_block_impl_0結構體所佔用的內存。

block執行內部代碼

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 30, 50);

簡化代碼:
block->FuncPtr(block, 30, 50)

複製代碼

經過上述代碼能夠發現調用block是經過block找到FunPtr直接調用,經過上面分析咱們知道block指向的是__main_block_impl_0類型結構體,可是咱們發現__main_block_impl_0結構體中並不直接就能夠找到FunPtr,而FunPtr是存儲在__block_impl中的,爲何block能夠直接調用__block_impl中的FunPtr呢?

從新查看上述源代碼能夠發現,(__block_impl *)block將block強制轉化爲__block_impl類型的,由於__block_impl是__main_block_impl_0結構體的第一個成員,至關於將__block_impl結構體的成員直接拿出來放在__main_block_impl_0中,那麼也就說明__block_impl的內存地址就是__main_block_impl_0結構體的內存地址開頭。因此能夠轉化成功。並找到FunPtr成員。

上面咱們知道,FunPtr中存儲着經過代碼塊封裝的函數地址,那麼調用此函數,也就是會執行代碼塊中的代碼。而且回頭查看__main_block_func_0函數,能夠發現第一個參數就是__main_block_impl_0類型的指針。也就是說將block傳入__main_block_func_0函數中,便於重中取出block捕獲的值。

總結

此時已經基本對block的底層結構有了基本的認識,上述代碼能夠經過一張圖展現其中各個結構體之間的關係

相關文章
相關標籤/搜索