block-變量的捕獲(capture)

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

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

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

一、自動變量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函數(shù)中的block,是我們常見的OC代碼,那我們從聲明 int age = 10;到將age的值改變age = 20,最后block輸出的age會等于20嗎
帶著這個疑問,我們來分析一下底層代碼的實現(xiàn),OC-->C++的轉(zhuǎn)換過程:cd到程序main.m的目錄下,執(zhí)行命令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;
}

這堆代碼,大家都很熟悉了吧,從上一章節(jié)講block-底層數(shù)據(jù)結構就分析過的,看看定義了int age = 10 的區(qū)別

auto變量的源碼.png

從上圖中的源碼中,我們可以留意到外部聲明的int age = 10在block里使用做的事情
為了能夠保證block正常訪問外部的變量,block有個變量捕獲機制,那什么類型會捕獲到block內(nèi)部,什么類型不會捕獲呢,我們來探討一下

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

二、靜態(tài)變量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就能修改了呢?我們來看一下下面的轉(zhuǎn)換成C++后的代碼,分析一下底層的實現(xiàn)。

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內(nèi)部,并且捕獲的是age的地址,所以外面age變成20的時候,block內(nèi)部輸出的age也是等于20,因為外部和block內(nèi)部的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,按常理來分析,全部變量的作用域是默認的情況下是所有的函數(shù)生命周期是程序結束時,所以block里打印等于20,應該大家都能理解,那我們來看一下block內(nèi)部有沒有捕獲到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內(nèi)部的實現(xiàn)沒有變化,就是沒有捕獲到block的內(nèi)部,和我們上面的猜想也是一樣的,因為age本來就是全部變量,作用域是所有函數(shù),生命周期是程序結束,所以哪里都能改,哪里都能輸出他的值,所以沒必要捕獲進block的內(nèi)部,并且無論block上面時候回調(diào),age的值都能正常輸出
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內(nèi)容