Objective-C底層探究之block(一)

iOS SDK 4.0開始,Apple引入了block這一特性。趁最近比較閑,來研究一下block底層實現方式。先來看一段簡單的代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b = 11;
        int (^block)(int,int) = ^(int a, int b) {
            return a+b;
        };
        block(a,b);
    }
    return 0;
}

在上面代碼中創建了一個帶有兩個參數的block并調用它。將代碼保存為 main.m文件使用命令

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

將代碼轉換為底層c++。會生成一個main.cpp文件。包含大約3w行代碼。直接看最后面關鍵部分:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

            return a+b;
        }

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;
        int b = 11;
        int (*block)(int,int) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);
    }
    return 0;
}

在main函數里,我們可以看到block變量創建方式。聲明block是一個指向int (*)(int,int)類型的函數指針。內部的值為

((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))

看起來很頭疼。我們來一步一步分解。
首先發現調用了一個函數

__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))

__main_block_impl_0 是什么東西呢,翻看main函數上面的代碼我們可以發現 __main_block_impl_0是一個結構體,內部為

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};

也就是說 我們的block本質上其實是一個指向 __main_block_impl_0結構體的指針。
該結構體里面有兩個變量:

  • struct __block_impl impl
  • struct __main_block_desc_0 * Desc

__block_impl__main_block_desc_0 類型 搜索上面代碼可以找到類型定義:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
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)};

可以看到 __block_impl 里面有一個isa指針。大膽猜測其實block本質也是一個Objective-C對象。
__main_block_desc_0里面儲存著__main_block_impl_0變量的大小也就是block變量所占的內存。

構造該結構體時傳入了兩個參數。第一個參數為__main_block_func_0再翻看上面代碼我們發現** __main_block_func_0**是一個函數

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

            return a+b;
        }

對比一下,發現函數里面實現跟我們原本block內部實現是一樣的。所以這個函數應該就是block本體。這個函數指針被傳入了** __main_block_impl_0**結構體里面的 impl 中,被 FuncPtr 變量持有。而第二個參數大概是一些描述信息。
到了調用時我們的調用被轉換成了:

((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, a, b);

又是一連串的類型轉換。我們慢慢來抽:
從括號里面大致可以看出

((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)

是一個函數頭,后面是傳入參數
繼續分析發現實質上是調用了

block->FuncPtr

這個函數,而通過前面我們得知FuncPtr函數實質上就是我們包裹在block中的代碼
這里有一個問題,我們知道block是一個__main_block_impl_0類型的對象。而這個類型里面直接拿不到FuncPtr的。要通過impl才能拿到的。那為什么這里可以這么寫呢。
我們知道C語言(包括C++)結構體本質是直接按照先后順序(整齊)排列在內存中的。而取結構體內部的本質就是內存偏移。impl變量是__main_block_impl_0類型的第一個變量。所以impl的初始內存地址就是block的初始內存地址。FuncPtrblock中偏移位置與在impl中偏移位置是一樣的。所以可以通過類型轉換

(__block_impl *)block)->FuncPtr

直接獲取FuncPtr這個函數調用。
以上

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容