Block本質(zhì)的探究

一、準(zhǔn)備工作

  • 1、創(chuàng)建一個(gè)命令行項(xiàng)目
  • 2、Mac自帶的終端Terminal

進(jìn)入創(chuàng)建好的項(xiàng)目,并在mian.m里面定義一個(gè)Block, 如下所示:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //定義一個(gè)block
        void (^myBlock)(void) = ^{
            NSLog(@"Hello Block!");
        };
        //block調(diào)用
        myBlock();
    }
    return 0;
}

打開(kāi)Terminal,cd到當(dāng)前項(xiàng)目main.m所在目錄,執(zhí)行以下指令:

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

上面的指令目的是借助clang編譯main.m得到編譯后的文件-main.cpp
我把編譯后的主要的代碼貼出來(lái),以便進(jìn)行后面的探究。

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
#pragma clang assume_nonnull end

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 void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
}

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; 

        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

二、定義Block探究

由于我們是在main函數(shù)里面定義的Block,所以我們?cè)?code>編譯后的文件里也是對(duì)應(yīng)從main函數(shù)開(kāi)始探究。
如果我們把代碼對(duì)應(yīng)起來(lái)的話就是下面這樣的:

  • 定義block,在編譯前和編譯后
    編譯前:
//定義一個(gè)block
 void (^myBlock)(void) = ^{
    NSLog(@"Hello Block!");
};

編譯后:

void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

編譯后去除一些強(qiáng)制轉(zhuǎn)換操作后:

void (*myBlock)(void) = &__main_block_impl_0(
                                              __main_block_func_0,
                                        &__main_block_desc_0_DATA));

經(jīng)過(guò)上面的簡(jiǎn)化我們不難發(fā)現(xiàn):

    1. 調(diào)用了__main_block_impl_0 (參數(shù)1, 參數(shù)2),并把返回值的地址(&)賦值給了myBlock。
  • 2、參數(shù)1是: __main_block_func_0
  • 3、參數(shù)2是: &__main_block_desc_0_DATA),把參數(shù)2的地址值(&)傳遞進(jìn)去了.
1、探究__main_block_impl_0

所以下一步,我們需要去看看__main_block_impl_0函數(shù)是什么?

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
//構(gòu)造函數(shù)(類似OC的init方法,把外面?zhèn)鬟M(jìn)來(lái)的參數(shù)賦值給自己的成員變量,并返回self),返回結(jié)構(gòu)體對(duì)象
  __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;
  }
};

從編譯后的文件里可以看到這個(gè)名為__main_block_impl_0 的 c++結(jié)構(gòu)體,里面有:

  • 一個(gè)構(gòu)造函數(shù) __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
  • 兩個(gè)主要的成員變量struct __block_impl implstruct __main_block_desc_0* Desc。

所以,我們不難得出,Block其實(shí)是一個(gè)結(jié)構(gòu)體對(duì)象

從外面?zhèn)鬟M(jìn)來(lái)的參數(shù)賦值給了它的兩個(gè)成員變量,所以下一步我們需要弄清楚,這兩個(gè)成員變量是什么。

2、探究__block_impl

在編譯后的文件中我們可以找到:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

__block_impl結(jié)構(gòu)體里面包含的:

  • void *isa, 結(jié)構(gòu)體的地址
  • void *FuncPtr, 函數(shù)的地址
  • int Flags,結(jié)構(gòu)體的標(biāo)識(shí)
  • int Reserved,是一個(gè)保留字段

看到這里,我們就不難發(fā)現(xiàn)這個(gè)Block的內(nèi)存地址其實(shí)就是 __block_impl 的 isa所指向的地址。

為什么這么說(shuō)?看下面我們貼出的__main_block_impl_0的結(jié)構(gòu)體,這里就不再細(xì)說(shuō)了

struct __main_block_impl_0 {
  struct __block_impl impl;
 ...
}
3、探究__main_block_desc_0

在編譯后的文件中我們可以找到:

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

size_t reserved,保留字段默認(rèn)值是0
size_t Block_size,存儲(chǔ)了Block的內(nèi)存大小,通過(guò)sizeof(struct __main_block_impl_0)計(jì)算出來(lái)的
也就是說(shuō),這個(gè)結(jié)構(gòu)體主要是來(lái)存儲(chǔ)Block的描述信息,如:內(nèi)存大小等。

4、看下_main_block_impl_0(...)構(gòu)造函數(shù)

現(xiàn)在清楚了Block的兩個(gè)成員變量后,我們就來(lái)看看它的構(gòu)造函數(shù)接收的參數(shù)。

_main_block_impl_0(
      void *fp, 
      struct __main_block_desc_0 *desc, 
      int flags=0
)

它接收3個(gè)參數(shù):

  • 1.void *fp,在函數(shù)里面把它賦值給了impl.FuncPtr
  • 2.struct __main_block_desc_0 *desc,在函數(shù)里面把它賦值給了Desc.
  • 1.int flags=0,這個(gè)是有一個(gè)默認(rèn)值,在上面的調(diào)用過(guò)程中沒(méi)有傳第三個(gè)參數(shù),說(shuō)明 當(dāng)前情況下使用默認(rèn)值就可以。

再來(lái)看下編譯后這個(gè)構(gòu)造函數(shù)接收的具體參數(shù):

void (*myBlock)(void) = &__main_block_impl_0(
                           __main_block_func_0,
                          &__main_block_desc_0_DATA));
5、探究__main_block_func_0

__main_block_func_0是block接收的第一個(gè)參數(shù),我們可以在編譯后的文件中找到

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
}

__main_block_func_0就是Block封裝了執(zhí)行邏輯的函數(shù),現(xiàn)在我們所看到的內(nèi)部封裝的要執(zhí)行的函數(shù)就是一開(kāi)始寫在Block里面的輸出函數(shù):NSLog(@"Hello Block!");

所以第一個(gè)參數(shù):把封裝了要執(zhí)行函數(shù)的函數(shù)地址傳給了__main_block_impl_0, 里面把函數(shù)地址賦值給了 impl.FuncPtr = fp;

6、探究__main_block_desc_0_DATA

__main_block_desc_0_DATA是block接收的第二個(gè)參數(shù),我們可以在編譯后的文件中找到:

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_DATA0傳給了__main_block_desc_0size_t reserved.
  • __main_block_desc_0_DATAsizeof(struct __main_block_impl_0)傳給了__main_block_desc_0size_t Block_size.

所以第二個(gè)參數(shù), 實(shí)際上是計(jì)算了這個(gè)Block的內(nèi)存大小,并把得到的這個(gè)結(jié)構(gòu)體的地址值傳遞進(jìn)去。

到此,結(jié)束了定義一個(gè)Block的本質(zhì)的探究。

三、Block調(diào)用的

首先,我們回看一下調(diào)用的代碼:

//block調(diào)用
myBlock();

編譯后

 ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

編譯后去除強(qiáng)制轉(zhuǎn)換就變成如下的代碼

 myBlock->FuncPtr(myBlock);

上面的分析我們知道:

  • myBlock__main_block_impl_0構(gòu)造函數(shù)創(chuàng)建完后返回的指針地址
  • myBlock->FuncPtr這句話的作用:
    通過(guò)myBlock的地址拿到impl,再通過(guò)impl拿到里面的FuncPtr保存的地址值,然后再調(diào)用方法
  • 傳進(jìn)去(myBlock)的地址就是傳給了封裝要執(zhí)行函數(shù)的函數(shù), 即 static void __main_block_func_0(struct __main_block_impl_0 *__cself)

所以到此也就完成了本次對(duì)Block本質(zhì)的探究!

四、總結(jié)一下

  • 1、Block本質(zhì)上也是一個(gè)OC對(duì)象,內(nèi)部也有一個(gè)isa指針。

  • 2、 Block是封裝了函數(shù)調(diào)用和函數(shù)調(diào)用環(huán)境的OC對(duì)象

  • 3、Block內(nèi)部的兩個(gè)主要成員:

    struct __block_impl impl,保存了Block的內(nèi)存地址,封裝要執(zhí)行函數(shù)的函數(shù)地址等
    struct __main_block_desc_0* Desc,內(nèi)部主要保存了Block的內(nèi)存大小。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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