《Objective-C 高級(jí)編程》干貨(1)-Blocks實(shí)質(zhì)

Blocks 概要

Blocks是C語言的擴(kuò)充功能??梢杂靡痪湓拋肀硎綛locks的擴(kuò)充功能:帶有自動(dòng)變量(局部變量)的匿名函數(shù)。

“帶有自動(dòng)變量值”究竟是什么呢。

先看看C函數(shù)中可能使用的變量。

  • 自動(dòng)變量(局部變量)
  • 函數(shù)的參數(shù)
  • 靜態(tài)變量(靜態(tài)局部變量)
  • 靜態(tài)全局變量
  • 全局變量
    其中,在函數(shù)中多次調(diào)用之間能夠傳遞值的變量有:
  • 靜態(tài)變量 (靜態(tài)局部變量)
  • 靜態(tài)全局變量
  • 全局變量
    雖然這些變量的作用域不同,但在整個(gè)程序當(dāng)中,一個(gè)變量總保持在一個(gè)內(nèi)存區(qū)域。因此,雖然多次調(diào)用函數(shù),但該變量值總能保持不變,在任何時(shí)候以任何狀態(tài)調(diào)用,使用的都是同樣的變量值。
    Blocks類型的變量可完全像通常的C語言變量一樣使用

Blocks底層結(jié)構(gòu)

首先我們通過clang將含有Blcoks語法的源代碼轉(zhuǎn)換為C++的源代碼,具體如下
1.新建.m文件

image.png

OC代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
            void (^blk)(void) = ^{
                printf("Block\n");
            };
        blk();
    }
    return 0;
}

2.打開終端 cd 到main.m所在文件夾
3.輸入clang -rewrite-objc main.m,就會(huì)在當(dāng)前文件夾內(nèi)自動(dòng)生成對(duì)應(yīng)的main.cpp文件
文件非常長(zhǎng),我們直接拉到最后,找到main函數(shù)
C++代碼

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

            void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}

首先來看最初的源代碼中的Block語法

^{
                printf("Block\n");
            };

可以看到,變換后的源代碼也含有相同的表達(dá)式

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

                printf("Block\n");
            }

該函數(shù)的參數(shù)*__cself 為指向Block的值的變量(相當(dāng)于C++里的this,OC里的self)
__cself是__main_block_impl_0 的指針

先看一下__main_block_impl_0 的結(jié)構(gòu)體,該結(jié)構(gòu)體我們可以從command+f 在main.cpp文件中搜索得到:

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

該結(jié)構(gòu)體的聲明如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
}

由于轉(zhuǎn)化源碼之后一并寫入了構(gòu)造函數(shù),所以看起來稍顯復(fù)雜,第一個(gè)成員變量是impl,我們先來看一下其 __block_impl impl結(jié)構(gòu)體的聲明

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

第二個(gè)成員變量是Desc指針,以下為__main_block_desc_0結(jié)構(gòu)體的聲明

static struct __main_block_desc_0 {
  size_t reserved;//今后版本升級(jí)所需的區(qū)域
  size_t Block_size;//Blcok的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

下面來看一初始化含有這些結(jié)構(gòu)體__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)

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

以上就是初始化 __main_block_impl_0結(jié)構(gòu)體的源代碼。
總結(jié)如圖


__main_block_impl_0結(jié)構(gòu)體源代碼.png

接下來先看一下該構(gòu)造函數(shù)的調(diào)用

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

去掉轉(zhuǎn)化部分為

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;

該源代碼將struct __main_block_impl_0 結(jié)構(gòu)體的自動(dòng)變量,既棧上生成的__main_block_impl_0 結(jié)構(gòu)體實(shí)例的指針賦值給__main_block_impl_0 結(jié)構(gòu)體指針類型的變量blk。

以下為這部分對(duì)應(yīng)的最初源代碼

     void (^blk)(void) = ^{
                printf("Block\n");
            };

將Block語法生成的Block賦給Block類型變量blk,它等同于將__main_block_impl_0結(jié)構(gòu)體實(shí)例指針賦值給變量blk。該源代碼中的Block就是__main_block_impl_0結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的__main_block_impl_0 結(jié)構(gòu)體實(shí)例

下面看下__main_block_impl_0結(jié)構(gòu)體實(shí)例構(gòu)造函數(shù)。

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

第一個(gè)參數(shù)是Block語法轉(zhuǎn)化的C函數(shù)指針。第二個(gè)參數(shù)是靜態(tài)全局變量初始化的__main_block_desc_0結(jié)構(gòu)體實(shí)例指針。以下為__main_block_desc_0結(jié)構(gòu)體實(shí)例的初始化部分代碼

__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};

由此可知,該源碼使用Block,即__main_block_impl_0結(jié)構(gòu)體實(shí)例的大小,進(jìn)行初始化。

__main_block_func_0 參數(shù)是有Blcoks語法轉(zhuǎn)換的C語言函數(shù)指針。
&__main_block_desc_0_DATA是作為__main_block_desc_0結(jié)構(gòu)體的實(shí)例指針。

我們來確認(rèn)下使用該Block的部分

   blk();

這部分可轉(zhuǎn)化為以下源代碼

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

去掉轉(zhuǎn)化部分

(*blk->impl.FuncPtr)(blk)

正如我們剛剛確認(rèn)的,有Block語法轉(zhuǎn)換的__main_block_func_0函數(shù)指針被賦值成員變量FuncPtr中。另外也說明了FuncPtr函數(shù)的參數(shù)
__cself指向了Block的值。在調(diào)用該函數(shù)的源代碼中可以看出Block正是作為參數(shù)進(jìn)行了傳遞。

Blcok即為OC的對(duì)象

截獲自動(dòng)變量值

Block解釋為帶有自動(dòng)變量(局部變量)的匿名函數(shù),帶有“自動(dòng)變量值”在Blocks中表現(xiàn)為“截獲自動(dòng)變量的值”。截獲自動(dòng)變量的值的實(shí)例如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d \n";
        void (^blk)(void) = ^{
            printf(fmt,val);
        };
        val = 2;
        fmt = "these values were change. val = %d \n";
        blk();

    }
    
    return 0;
}

執(zhí)行結(jié)果

val = 10 

該源代碼中Block語法的表達(dá)式使用的是它之前聲明的自動(dòng)變量fmt和val。Blocks中,Block表達(dá)式獲取所使用的自動(dòng)變量的值,即保存該自動(dòng)變量的瞬間值。所以在執(zhí)行Block語法后即使改寫B(tài)lcok中使用的自動(dòng)變量值也不會(huì)影響B(tài)lcok執(zhí)行時(shí)自動(dòng)變量的值。
這就是自動(dòng)變量值的截獲。

通過clang -rewrite-objc main.m 分析下源代碼
C++代碼

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d \n";
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
        val = 2;
        fmt = "these values were change. val = %d \n";
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    }

    return 0;
}

__main_block_impl_0結(jié)構(gòu)體

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我們注意到,Blcok語法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到了 __main_block_impl_0結(jié)構(gòu)體中,請(qǐng)注意沒有使用的自動(dòng)變量不會(huì)增加如dmy。

在轉(zhuǎn)換后的源代碼中,截獲到 _main_block_impl_0結(jié)構(gòu)體實(shí)例的成員變量上的自動(dòng)變量,這些變量在Block語法表達(dá)式之前被定義。
因此 所謂“截獲自動(dòng)變量值”意味著在執(zhí)行Block語法時(shí),Block語法表達(dá)式所使用的自動(dòng)變量值被保存到了Block的結(jié)構(gòu)體實(shí)例(即Blcok自身)中
若想在Blcok語法的表達(dá)式中將值付給在Block語法外的自動(dòng)變量,需要在該自動(dòng)變量上附加
_block說明符。

Block與_ _block變量的實(shí)質(zhì).png

最后編輯于
?著作權(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)容