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

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

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

在上面代碼中創(chuàng)建了一個(gè)帶有兩個(gè)參數(shù)的block并調(diào)用它。將代碼保存為 main.m文件使用命令

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

將代碼轉(zhuǎn)換為底層c++。會(huì)生成一個(gè)main.cpp文件。包含大約3w行代碼。直接看最后面關(guān)鍵部分:

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函數(shù)里,我們可以看到block變量創(chuàng)建方式。聲明block是一個(gè)指向int (*)(int,int)類型的函數(shù)指針。內(nèi)部的值為

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

看起來很頭疼。我們來一步一步分解。
首先發(fā)現(xiàn)調(diào)用了一個(gè)函數(shù)

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

__main_block_impl_0 是什么東西呢,翻看main函數(shù)上面的代碼我們可以發(fā)現(xiàn) __main_block_impl_0是一個(gè)結(jié)構(gòu)體,內(nèi)部為

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本質(zhì)上其實(shí)是一個(gè)指向 __main_block_impl_0結(jié)構(gòu)體的指針。
該結(jié)構(gòu)體里面有兩個(gè)變量:

  • 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 里面有一個(gè)isa指針。大膽猜測(cè)其實(shí)block本質(zhì)也是一個(gè)Objective-C對(duì)象。
__main_block_desc_0里面儲(chǔ)存著__main_block_impl_0變量的大小也就是block變量所占的內(nèi)存。

構(gòu)造該結(jié)構(gòu)體時(shí)傳入了兩個(gè)參數(shù)。第一個(gè)參數(shù)為__main_block_func_0再翻看上面代碼我們發(fā)現(xiàn)** __main_block_func_0**是一個(gè)函數(shù)

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

            return a+b;
        }

對(duì)比一下,發(fā)現(xiàn)函數(shù)里面實(shí)現(xiàn)跟我們?cè)綽lock內(nèi)部實(shí)現(xiàn)是一樣的。所以這個(gè)函數(shù)應(yīng)該就是block本體。這個(gè)函數(shù)指針被傳入了** __main_block_impl_0**結(jié)構(gòu)體里面的 impl 中,被 FuncPtr 變量持有。而第二個(gè)參數(shù)大概是一些描述信息。
到了調(diào)用時(shí)我們的調(diào)用被轉(zhuǎn)換成了:

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

又是一連串的類型轉(zhuǎn)換。我們慢慢來抽:
從括號(hào)里面大致可以看出

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

是一個(gè)函數(shù)頭,后面是傳入?yún)?shù)
繼續(xù)分析發(fā)現(xiàn)實(shí)質(zhì)上是調(diào)用了

block->FuncPtr

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

(__block_impl *)block)->FuncPtr

直接獲取FuncPtr這個(gè)函數(shù)調(diào)用。
以上

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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