默認(rèn)情況下限匣,在block中訪問(wèn)的外部變量是復(fù)制過(guò)去的邦危,即:寫(xiě)操作不對(duì)原變量生效醉途。但是你可以加上 __block
來(lái)讓其寫(xiě)操作生效捕犬,示例代碼如下:
__block int a = 0;
void (^foo)(void) = ^{
a = 1;
};
foo(); //這里跷坝,a的值被修改為1
你同樣可以在面試中這樣回答,但你并沒(méi)有答到“點(diǎn)子上”或听。真正的原因探孝,并沒(méi)有書(shū)這本書(shū)里寫(xiě)的這么“神奇”笋婿,而且這種說(shuō)法也有點(diǎn)牽強(qiáng)誉裆。面試官肯定會(huì)追問(wèn)“為什么寫(xiě)操作就生效了?”真正的原因是這樣的:
我們都知道:Block不允許修改外部變量的值缸濒,這里所說(shuō)的外部變量的值足丢,指的是棧中指針的內(nèi)存地址粱腻。__block 所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中斩跌。進(jìn)而在block內(nèi)部也可以修改外部變量的值绍些。
Block不允許修改外部變量的值。Apple這樣設(shè)計(jì)耀鸦,應(yīng)該是考慮到了block的特殊性柬批,block也屬于“函數(shù)”的范疇,變量進(jìn)入block袖订,實(shí)際就是已經(jīng)改變了作用域氮帐。在幾個(gè)作用域之間進(jìn)行切換時(shí),如果不加上這樣的限制洛姑,變量的可維護(hù)性將大大降低上沐。又比如我想在block內(nèi)聲明了一個(gè)與外部同名的變量,此時(shí)是允許呢還是不允許呢楞艾?只有加上了這樣的限制参咙,這樣的情景才能實(shí)現(xiàn)。于是棧區(qū)變成了紅燈區(qū)硫眯,堆區(qū)變成了綠燈區(qū)蕴侧。
我們可以打印下內(nèi)存地址來(lái)進(jìn)行驗(yàn)證:
__block int a = 0;
NSLog(@"定義前:%p", &a); //棧區(qū)
void (^foo)(void) = ^{
a = 1;
NSLog(@"block內(nèi)部:%p", &a); //堆區(qū)
};
NSLog(@"定義后:%p", &a); //堆區(qū)
foo();
2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定義前:0x16fda86f8
2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定義后:0x155b22fc8
2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] block內(nèi)部: 0x155b22fc8
“定義后”和“block內(nèi)部”兩者的內(nèi)存地址是一樣的,我們都知道 block 內(nèi)部的變量會(huì)被 copy 到堆區(qū)舟铜,“block內(nèi)部”打印的是堆地址戈盈,因而也就可以知道,“定義后”打印的也是堆的地址谆刨。
那么如何證明“block內(nèi)部”打印的是堆地址塘娶?
把三個(gè)16進(jìn)制的內(nèi)存地址轉(zhuǎn)成10進(jìn)制就是:
定義后前:6171559672
block內(nèi)部:5732708296
定義后后:5732708296
中間相差438851376個(gè)字節(jié),也就是 418.5M 的空間痊夭,因?yàn)槎训刂芬∮跅5刂返蟀叮忠驗(yàn)閕OS中一個(gè)進(jìn)程的棧區(qū)內(nèi)存只有1M,Mac也只有8M她我,顯然a已經(jīng)是在堆區(qū)了虹曙。
這也證實(shí)了:a 在定義前是棧區(qū),但只要進(jìn)入了 block 區(qū)域番舆,就變成了堆區(qū)酝碳。這才是 __block 關(guān)鍵字的真正作用。
__block 關(guān)鍵字修飾后恨狈,int類(lèi)型也從4字節(jié)變成了32字節(jié)疏哗,這是 Foundation 框架 malloc 出來(lái)的。這也同樣能證實(shí)上面的結(jié)論禾怠。(PS:居然比 NSObject alloc 出來(lái)的 16 字節(jié)要多一倍)返奉。
理解到這是因?yàn)槎褩5刂返淖兏锤椋撬^的“寫(xiě)操作生效”,這一點(diǎn)至關(guān)重要芽偏,要不然你如何解釋下面這個(gè)現(xiàn)象:
以下代碼編譯可以通過(guò)雷逆,并且在block中成功將a的從Tom修改為Jerry。
NSMutableString *a = [NSMutableString stringWithString:@"Tom"];
NSLog(@"\n 定以前:------------------------------------\n\
a指向的堆中地址:%p污尉;a在棧中的指針地址:%p", a, &a); //a在棧區(qū)
void (^foo)(void) = ^{
a.string = @"Jerry";
NSLog(@"\n block內(nèi)部:------------------------------------\n\
a指向的堆中地址:%p膀哲;a在棧中的指針地址:%p", a, &a); //a在棧區(qū)
a = [NSMutableString stringWithString:@"William"];
};
foo();
NSLog(@"\n 定以后:------------------------------------\n\
a指向的堆中地址:%p;a在棧中的指針地址:%p", a, &a); //a在棧區(qū)
這里的a已經(jīng)由基本數(shù)據(jù)類(lèi)型被碗,變成了對(duì)象類(lèi)型等太。block會(huì)對(duì)對(duì)象類(lèi)型的指針進(jìn)行copy,copy到堆中蛮放,但并不會(huì)改變?cè)撝羔標(biāo)赶虻亩阎械牡刂匪趼眨栽谏厦娴氖纠a中,block體內(nèi)修改的實(shí)際是a指向的堆中的內(nèi)容包颁。
但如果我們嘗試像上面圖片中的65行那樣做瞻想,結(jié)果會(huì)編譯不通過(guò),那是因?yàn)榇藭r(shí)你在修改的就不是堆中的內(nèi)容娩嚼,而是棧中的內(nèi)容蘑险。
上文已經(jīng)說(shuō)過(guò):Block不允許修改外部變量的值,這里所說(shuō)的外部變量的值岳悟,指的是棧中指針的內(nèi)存地址佃迄。棧區(qū)是紅燈區(qū),堆區(qū)才是綠燈區(qū)贵少。