iOS底層原理之Block

前言

Block 是 C 語(yǔ)言的擴(kuò)充功能, Apple 在 iOS4 引入了這個(gè)新功能. 一句話形容 Block, 那就是帶有自動(dòng)變量(局部變量)的匿名函數(shù).

在 OC 中實(shí)現(xiàn)代碼如下

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

結(jié)構(gòu)圖

在 Block_layout 中有 isa 指針, 所以 Block 在 OC 中是按照對(duì)象來(lái)處理的. 常見(jiàn)的 Block 有 3 種類型, 分別是 _NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock.

研究工具: clang命令

為了研究編譯器的實(shí)現(xiàn)原理, 需要使用 clang 命令. clang 命令可以將 OC 的源碼轉(zhuǎn)換為 C/C++ 語(yǔ)言的代碼.

clang -rewrite-objc 文件名

一. Block 捕獲外界變量的本質(zhì)

C語(yǔ)言中變量分為以下幾種:

  • 自動(dòng)變量
  • 函數(shù)參數(shù)
  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量

先來(lái)一段測(cè)試代碼

int global_i = 1;//全局變量

static int static_global_j = 2;//靜態(tài)全局變量

int main(int argc, const char * argv[]) {
    
    static int static_k = 3;//靜態(tài)變量
    int val = 4;//自動(dòng)變量
    int val2 = 5;
    void(^myBlcok)(void) = ^ {
        global_i++;
        static_global_j++;
        static_k++;
        //val++;
        NSLog(@"中: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
        NSLog(@"%p", &val2);
    };
    global_i++;
    static_global_j++;
    static_k++;
    val++;
    NSLog(@"前: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
    NSLog(@"%@", myBlcok);
    NSLog(@"%p", &val2);
    myBlcok();
    NSLog(@"后: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
    return 0;
}


這段代碼運(yùn)行的時(shí)候會(huì)報(bào)錯(cuò),提示說(shuō)自動(dòng)變量沒(méi)有加上__block修飾.

用 clang 轉(zhuǎn)換的到.cpp文件,源碼如下

int global_i = 1;

static int static_global_j = 2;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_k;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
    int val = __cself->val; // bound by copy:此處Block僅僅捕獲了val值,并沒(méi)有捕獲val的內(nèi)存地址.所以在這個(gè)函數(shù)中即使重寫(xiě)這個(gè)val的值,依舊無(wú)法改變Block外面val的值.因此OC在編譯層面就杜絕了這種錯(cuò)誤,在Block中無(wú)法改變自動(dòng)變量的值,編譯器會(huì)報(bào)錯(cuò).

        global_i++;
        static_global_j++;
        (*static_k)++;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_b6_dgrlb0m175gd2m38vy8qgxzw0000gp_T_main_7d7194_mi_0, global_i, static_global_j, (*static_k), val);
    }

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[]) {

    static int static_k = 3;

   int val = 4;

    void(*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
    global_i++;
    static_global_j++;
    static_k++;
    val++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_b6_dgrlb0m175gd2m38vy8qgxzw0000gp_T_main_7d7194_mi_1, global_i, static_global_j, static_k, val);
    ((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
    return 0;
}

首先全局變量 global_i 和靜態(tài)變量 static_global_j 的值增加,是因?yàn)樗麄兪侨值?作用域很廣,所以 Block 捕獲了它們之后,在 Block 里面進(jìn)行 ++ 操作, Block 結(jié)束后,它們的值可以保存下來(lái).

靜態(tài)變量 static_k 和自動(dòng)變量 i
__main_block_func_0 中可以看到 靜態(tài)變量 static_k 和 自動(dòng)變量 i 被 Block 從外部捕捉進(jìn)來(lái),成為__main_block_func_0 這個(gè)結(jié)構(gòu)體的成員變量了.

如果 Block 外面有很多變量,但這些變量并不會(huì)在 Block 里面使用到,那么這些變量不會(huì)被 Block 捕獲進(jìn)來(lái).

__main_block_func_0 中, 自動(dòng)變量 val 雖然被捕獲進(jìn)來(lái)了, 但是是用 __cself->val 訪問(wèn)的. Block 只是獲取了 val 的值,并沒(méi)有獲取到存放 val 的內(nèi)存地址.所以在__main_block_func_0 這個(gè)函數(shù)中改變 val 的值,依舊無(wú)法改變 Block 外部自動(dòng)變量 val 的值.

4種變量中只有 靜態(tài)變量 , 靜態(tài)全局變量 , 全局變量 這3種是可以在 Block 里面被改變值的.

  1. 靜態(tài)全局變量,全局變量由于作用域的原因,可以在 Block 里面被改變,存儲(chǔ)在全局區(qū)
存儲(chǔ)圖
  1. 靜態(tài)變量傳遞給 Block 的是內(nèi)存地址值,所以能在 Block 里面直接改變值.

  2. 對(duì)于自動(dòng)變量而言,要想在 Block 里面改變自動(dòng)變量的值,需要在自動(dòng)變量前加上 __block 關(guān)鍵字(改變存儲(chǔ)區(qū)).

小結(jié):

在 Block 中改變變量值有2種方式, 一是傳遞內(nèi)存地址指針到 Block 中, 二是改變存儲(chǔ)區(qū)域.

二. Block 的 copy 和 dispose

在 OC 中, 一般 Block 分為3種, _NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobalBlock.

先來(lái)比較一下區(qū)別,

  • _NSConcreteStackBlock
    只用到外部局部變量,成員屬性變量,并且沒(méi)有強(qiáng)指針應(yīng)用的 Block 都是 StackBlock. 生命周期由系統(tǒng)管理.
  • _NSConcreteMallocBlock
    有強(qiáng)指針引用或者使用 copy 修飾的成員屬性引用的 Block 會(huì)被復(fù)制一份到堆區(qū)成為 MallocBlock, 沒(méi)有強(qiáng)指針引用即銷毀, 生命周期由開(kāi)發(fā)者控制.
  • _NSConcreteGlobalBlock
    沒(méi)有用到外界變量或者只用到全局變量,靜態(tài)變量的 Block 都是 GlobalBlock , 生命周期從創(chuàng)建開(kāi)始到程序結(jié)束.
?著作權(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)容