block-變量的捕獲(capture)

爲了能夠保證block正常訪問外部的變量,block有個變量捕獲機制,如下圖

捕獲的變量

auto:自動變量,平時我們定義int age = 10,前面有個auto,auto int age = 10,系統幫我們默認的加上了一個auto。—–值傳遞—–
static:靜態變量 —–指針傳遞—–
全局變量 —–直接訪問—–

根據上面結論,我們一個個展開討論和分析。

一、自動變量auto修飾的變量,auto int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        auto int age = 10;

        void (^block)(void) = ^() {
            NSLog(@"age = %d,",age);
        };

        age = 20;
        block();
    }
    return 0;
}

此main函數中的block,是我們常見的OC代碼,那我們從聲明 int age = 10;到將age的值改變age = 20,最後block輸出的age會等於20嗎
帶着這個疑問,我們來分析一下底層代碼的實現,OC–>C++的轉換過程:cd到程序main.m的目錄下,執行命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m,可以得到一個main.cpp文件,拖到工程目錄中。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_0xn052bn6dgb9z7pfk8bbg740000gn_T_main_6a0dd1_mi_0,age);
        }

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 age = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

這堆代碼,大家都很熟悉了吧,從上一章節講block-底層數據結構就分析過的,看看定義了int age = 10 的區別auto變量的源碼.png
從上圖中的源碼中,我們可以留意到外部聲明的int age = 10在block裏使用做的事情
爲了能夠保證block正常訪問外部的變量,block有個變量捕獲機制,那什麼類型會捕獲到block內部,什麼類型不會捕獲呢,我們來探討一下

  • 1.在main函數中,在block定義時,block將外部的age當做參數傳給了__main_block_impl_0
  • 2.__main_block_impl_0已經將外面聲明age捕捉賦值到block內部,既block內部也定義了一個int age;類型
  • 3.block把main函數定義的int age = 10的值當做參數賦值給了block裏面的age
  • 4.此時改變age的值,只是改變main函數裏的age的值,無法改變block內部的age的值,兩個是屬於不同函數體內的age,互不相關。
  • 5.block()調用時,輸出的age = __cselef->age,就是等於block內部(__main_block_impl_0)的age值
auto變量總結,由上面的分析,我們可驗證自動變量auto修飾的變量,是有捕捉到block內部,並且是屬於值傳遞。

二、靜態變量static修飾的變量,static int age = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        static int age = 10;

        void (^block)(void) = ^() {
            NSLog(@"age = %d,",age);
        };

        age = 20;
        block();
    }
    return 0;
}

// 控制檯的輸出
2018-06-13 15:30:13.674073+0800 block-變量的捕獲[92711:10064124] age = 20,
Program ended with exit code: 0

static修飾的變量age,打印的時候age = 20,爲什麼用static就能修改了呢?我們來看一下下面的轉換成C++後的代碼,分析一下底層的實現。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_0xn052bn6dgb9z7pfk8bbg740000gn_T_main_73b8c0_mi_0,(*age));
        }

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; 

        static int age = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));

        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

static變量的源碼.png

static變量總結,由上圖的分析,我們可以得知,static 修飾的age,是會捕獲進block內部,並且捕獲的是age的地址,所以外面age變成20的時候,block內部輸出的age也是等於20,因爲外部和block內部的age指向的是同一地址。

三、全局變量


int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void (^block)(void) = ^() {
            NSLog(@"age = %d,",age);
        };

        age = 20;
        block();
    }
    return 0;
}
// 控制檯的打印
2018-06-13 16:08:54.737079+0800 block-變量的捕獲[96358:10119188] age = 20,
Program ended with exit code: 0

當age時全局變量的時候,打印的結果爲20,按常理來分析,全部變量的作用域是默認的情況下是所有的函數生命週期是程序結束時,所以block裏打印等於20,應該大家都能理解,那我們來看一下block內部有沒有捕獲到age呢,分析一下源碼,如下

int age = 10;

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_5l_0xn052bn6dgb9z7pfk8bbg740000gn_T_main_164d03_mi_0,age);
        }

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 (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
全局變量總結:從上面源碼可以看出,block內部的實現沒有變化,就是沒有捕獲到block的內部,和我們上面的猜想也是一樣的,因爲age本來就是全部變量,作用域是所有函數,生命週期是程序結束,所以哪裏都能改,哪裏都能輸出他的值,所以沒必要捕獲進block的內部,並且無論block上面時候回調,age的值都能正常輸出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章