又見block(三):block實質

首先來看看block截獲自動變量(外部局部變量)的幾種情況

block“帶有自動變量(截獲局部變量)”的含義在於具有“截獲自動變量值”的能力

ARC中常見的block用法場景如下:
這裏寫圖片描述
由以上結果總結如下:

  • 默認block的類型是NSGlobalBlock,一旦在block中引用自動變量(包括OC對象),無論是否修改,block類型變爲NSMallocBlock
  • 默認情況下,在block內不能對截獲的自動變量(或OC對象)進行修改,編譯器會報錯
  • 默認情況下,在block中引用的自動變量或OC對象(包括調用OC對象方法),block中的自動變量地址不同,而引用前後自動變量的地址一致
  • 在自動變量(或OC對象)前添加__block,無論是否修改自動變動,引用後與block自動變量的地址一致,而且與引用前不一致

另外,需要注意的一點是,block目前沒有實現對C語言數組的截獲,例如以下代碼會產生編譯錯誤
這裏寫圖片描述
使用指針可以解決這個問題

- (void)blockCaptureCArray {
    //  C 語言的數組
    const char *text = "hello";
    void (^blockTest) (void) = ^{
        NSLog(@"%c",text[0]);
    };
    blockTest();
    }

block的實質

可以通過clang -rewrite-objc 源代碼文件名 將含有block的代碼變換爲C++的源代碼進行分析,將如下main.m文件

#import <stdio.h>
    int main() {
        void (^blk) (void) = ^{
            printf("Block\n");
        };
        blk();
        return 0;
    }

打開終端,在main.m所在目錄下鍵入clang -rewrite-objc main.m即可在當前目錄下生成一個main.cpp文件,主要分爲:

1、__block_impl結構體定義

    //  結構體 __block_impl 的聲明
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

2、__main_block_impl_0結構體定義,其中包含其構造函數

參數fp傳入__main_block_func_0函數的地址,賦值給__block_impl類型的結構體實例impl的函數指針FuncPtr
參數desc傳入的&__main_block_desc_0_DATA結構體取地址賦值給了__main_block_desc_0類型的結構體指針Desc
__block_impl類型的結構體實例implisa指針存放了_NSConcreteStackBlock類的地址,旨在將block作爲OC的對象處理,將block類的信息放置於_NSConcreteStackBlock,由此可見,block的實質是OC對象

    //  結構體 __main_block_impl_0 的聲明
struct __main_block_impl_0 {
    //  成員變量 impl
  struct __block_impl impl;
    //  指針
  struct __main_block_desc_0* Desc;

    //  結構體 __main_block_impl_0 的構造函數
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

3、block函數轉化後的源代碼

    //  __cself 是 __main_block_impl_0 結構體的指針
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
    }
    //  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)
};

4、程序執行的main函數

/* main.m 中 main 函數轉換後的源代碼*/
int main() {
    //  左邊是一個無參無返回值的函數指針
    //  右邊__main_block_impl_0結構體包含自己的屬性、構造方法、成員方法,然後取其地址
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    //  代碼轉換1
    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    // 對應代碼 block();
    // 前半部分訪問結構體 __block_impl 自己的函數指針 FuncPtr,並且將 block 自身作爲參數傳遞
    //  代碼轉換2
    (*blk->impl.FuncPtr)(blk);
    return 0;
}

又見block(二):block語法定義
又見block(四):block捕獲自動變量
又見block(五): __block變量和對象
又見block(六):block存儲域與__block變量存儲域
又見block(七):截獲對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章