iOS Block的本質(一)

iOS Block的本質(一)

1.對block有一個基本的認識

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

2.探尋block的本質

  • 首先寫一個簡單的block
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(int, int) =  ^(int a , int b){
            NSLog(@"this is a block! -- %d", age);
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
        };

        block(10, 10);
    }
    return 0;
}

3.查看其內部結構

  1. 使用命令行將代碼轉化爲c++與OC代碼進行比較
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令代碼ios

    // 編譯後代碼
      int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            int age = 10;
            void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
        }
        return 0;
    }
  2. 從以上c++代碼中block的聲明和定義分別與oc代碼中相對應顯示。將c++中block的聲明和調用分別取出來查看其內部實現。
  3. 定義block變量代碼c++

    // 定義block變量代碼
        void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        // 能夠簡化爲下列
        // void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);
  4. 上述定義代碼中,能夠發現,block定義中調用了__main_block_impl_0函數,而且將__main_block_impl_0函數的地址賦值給了block。那麼咱們來看一下__main_block_impl_0函數內部結構。sass

  5. __main_block_imp_0結構體iphone

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age;
      __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;
      }
    };
  6. __main_block_imp_0結構體內有一個同名構造函數__main_block_imp_0,構造函數中對一些變量進行了賦值最終會返回一個結構體。
    • 那麼也就是說最終將一個__main_block_imp_0結構體的地址賦值給了block變量
    • __main_block_impl_0結構體內能夠發現__main_block_impl_0構造函數中傳入了四個參數。(void *)__main_block_func_0、&__main_block_desc_0_DATA、age、flags。其中flage有默認值,也就說flage參數在調用的時候能夠省略不傳。而最後的 age(_age)則表示傳入的_age參數會自動賦值給age成員,至關於age = _age。
    • 接下來着重看一下前面三個參數分別表明什麼。
    1. (void *)__main_block_func_0參數函數

      ```C++
       static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
           int age = __cself->age; // bound by copy
               NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
               NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_1);
               NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_2);
               NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_3);
           }
       ```
      • 在__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的構造函數中保存在結構體內。
    2. &__main_block_desc_0_DATA參數this

      C++ 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。
    3. age參數
      • age也就是咱們定義的局部變量。由於在block塊中使用到age局部變量,因此在block聲明的時候這裏纔會將age做爲參數傳入,也就說block會捕獲age,若是沒有在block中使用age,這裏將只會傳入(void *)__main_block_func_0,&__main_block_desc_0_DATA兩個參數。
        這裏能夠根據源碼思考一下爲何當咱們在定義block以後修改局部變量age的值,在block調用的時候沒法生效。
      • 由於block在定義的以後已經將age的值傳入存儲在__main_block_imp_0結構體中並在調用的時候將age從block中取出來使用,所以在block定義以後對局部變量進行改變是沒法被block捕獲的。
  7. 此時回過頭來查看__main_block_impl_0結構體3d

    struct __main_block_impl_0 {
          struct __block_impl impl;
          struct __main_block_desc_0* Desc;
          int age;
          __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;// block 內部代碼塊地址
            Desc = desc;// 存儲block 對象佔用的內存大小
          }
        };
  8. 首先咱們看一下__block_impl第一個變量就是__block_impl結構體。指針

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

  10. 接着經過上面對__main_block_impl_0結構體構造函數三個參數的分析咱們能夠得出結論:
    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結構體所佔用的內存。
  11. 調用block執行內部代碼

    ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
  12. 經過上述代碼能夠發現調用block是經過block找到FunPtr直接調用,經過上面分析咱們知道block指向的是__main_block_impl_0類型結構體,可是咱們發現__main_block_impl_0結構體中並不直接就能夠找到FunPtr,而FunPtr是存儲在__block_impl中的,爲何block能夠直接調用__block_impl中的FunPtr呢?
  13. 從新查看上述源代碼能夠發現,(__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成員。
  14. 上面咱們知道,FunPtr中存儲着經過代碼塊封裝的函數地址,那麼調用此函數,也就是會執行代碼塊中的代碼。而且回頭查看__main_block_func_0函數,能夠發現第一個參數就是__main_block_impl_0類型的指針。也就是說將block傳入__main_block_func_0函數中,便於重中取出block捕獲的值。

3.驗證block的本質

  • 驗證block的本質是__main_block_impl_0結構體類型。
  1. 經過代碼證實一下上述內容:

    #import <Foundation/Foundation.h>
    
    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    };
    
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int age;
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            void (^block)(int, int) =  ^(int a , int b){
                NSLog(@"this is a block! -- %d", age);
                NSLog(@"this is a block!");
                NSLog(@"this is a block!");
                NSLog(@"this is a block!");
            };
            // 將底層的結構體強制轉化爲咱們本身寫的結構體,經過咱們自定義的結構體探尋block底層結構體
            struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
            block(10, 10);
        }
        return 0;
    }
  2. 經過打斷點能夠看出咱們自定義的結構體能夠被賦值成功,以及裏面的值。

  3. 接下來斷點來到block代碼塊中,看一下堆棧信息中的函數調用地址。Debuf workflow -> always show Disassembly

  4. 經過上圖能夠看到地址確實和FuncPtr中的代碼塊地址同樣。

總結

  • block的原理是怎樣的?本質是什麼?
    • block本質上也是一個OC對象,它內部也有個isa指針
    • block是封裝了函數調用以及函數調用環境的OC對象
    • block的底層結構以下圖所示

引用

相關文章
相關標籤/搜索