Block由淺入深(3):Block捕獲局部變量

Block訪問(wèn)外部變量

上一篇文章我們使用了一個(gè)最簡(jiǎn)單的Block的例子說(shuō)明Block是一個(gè)對(duì)象纪岁,但是我們平時(shí)使用的Block大部分是帶有參數(shù)的弱左,或者是能夠訪問(wèn)到Block外部的局部變量的勃救,那么這種類型的Block是怎么實(shí)現(xiàn)的呢永部?
我們首先看一個(gè)訪問(wèn)外部局部變量的例子:

int main()
{
    int val = 10;
    const char* fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val);};

    val = 20;

    blk();

    return 0;
}

也許很多人會(huì)以為這段代碼輸出結(jié)果是:val = 20依鸥,其實(shí)這段代碼輸出結(jié)果是val = 10黄橘。
為什么呢兆览?
因?yàn)樵贐lock實(shí)現(xiàn)的時(shí)候,Block中使用的局部變量已經(jīng)被“捕獲”了塞关。

何為“捕獲”

我們?cè)撛趺蠢斫狻安东@”呢抬探?我們?cè)僖淮渭莱錾弦黄慕K極武器——clang,通過(guò)clang轉(zhuǎn)化后的主要代碼如下:

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
printf(fmt, 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 val = 10;
    const char* fmt = "val = %d\n";
    void (*blk)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

    val = 20;

    blk->FuncPtr(blk);

    return 0;
}

根據(jù)上一篇講述的內(nèi)容帆赢,我們可以發(fā)現(xiàn)Block的實(shí)現(xiàn)是struct __main_block_impl_0小压,這個(gè)結(jié)構(gòu)體比上一篇的結(jié)構(gòu)體多了兩個(gè)成員變量fmtval,這兩個(gè)成員變量在構(gòu)造函數(shù)中就已經(jīng)被賦值了椰于。而在main函數(shù)中怠益,這個(gè)結(jié)構(gòu)體的構(gòu)造函數(shù)調(diào)用時(shí)機(jī)是在val變量賦值為20之前。所以即使我們給val賦值為20了瘾婿,因?yàn)?code>__main_block_impl_0構(gòu)造函數(shù)已經(jīng)在賦值之前調(diào)用了蜻牢,__main_block_impl_0的結(jié)構(gòu)體成員變量val已經(jīng)被賦值為10了,也就是說(shuō)val = 20這行代碼偏陪,改變的是main函數(shù)中的val局部變量的值抢呆,而不是Block對(duì)象中val變量的值。
所謂“捕獲”其實(shí)質(zhì)是Block體內(nèi)的變量與被Block引用的外部局部變量是兩個(gè)不同的變量笛谦,它們有不同的作用域抱虐,有不同的存儲(chǔ)空間,它們的值早在Block實(shí)現(xiàn)時(shí)就已經(jīng)確定好了饥脑,而不是在Block執(zhí)行時(shí)才被確定的恳邀。
我們可以用下面的圖描述一下上述代碼執(zhí)行時(shí)內(nèi)存的變化過(guò)程:

內(nèi)存變化

上面的結(jié)論我們可以通過(guò)如下代碼驗(yàn)證:

int val = 10;
const char* fmt = "val = %d\n";
printf("address of val = 0x%lx, address of fmt = 0x%lx\n", &val, &fmt);
void (^blk1)(void) = ^{
    printf("address of val = 0x%lx, address of fmt = 0x%lx\n", &val, &fmt);
    printf(fmt, val);
};

val = 20;
    
blk1();

上述代碼輸出結(jié)果如下:


地址不同

從輸出結(jié)果也可以看出,兩個(gè)val和fmt的變量地址是不同的灶轰,說(shuō)明它們是兩個(gè)不同的變量轩娶。

Block捕獲指針

看到上面的結(jié)論,也許有人會(huì)疑惑:為什么我在編程的時(shí)候給一個(gè)局部對(duì)象的成員變量賦值后框往,再調(diào)用Block鳄抒,得到的是賦值后的值呢?
我們將上面的例子修改一下:

@interface BlockTest : NSObject

@property(nonatomic, assign) int num;

@end

@implementation BlockTest

@end

int main(int argc, const char * argv[]) {
    BlockTest *test = [[BlockTest alloc] init];
    test.num = 10;
    const char *fmt = "val = %d\n";
    void(^blk)(void) = ^{
      printf(fmt, test.num);
    };

    test.num = 20;
    
    blk();
    
    return 0;
}

這時(shí)候輸出的結(jié)果是val = 20。這是為什么呢许溅?
我們需要搞清楚Objective-C對(duì)象的內(nèi)存管理機(jī)制瓤鼻。在這個(gè)例子中,雖然main函數(shù)中的test和Block里的test是不同的對(duì)象贤重,但是它們指向的確是同一個(gè)對(duì)象的實(shí)現(xiàn)茬祷,因?yàn)锽lock里的test對(duì)象不是通過(guò)copy來(lái)賦值的,而是通過(guò)strong引用來(lái)賦值的(在非ARC環(huán)境下是assign引用并蝗,類似于ARC環(huán)境下的weak引用)祭犯,所以我們?cè)趍ain函數(shù)中修改了對(duì)象的屬性,也會(huì)作用到Block對(duì)象里的test成員滚停。

總結(jié)

通過(guò)本篇的講解沃粗,我們了解到Block內(nèi)部的變量與Block外部的變量實(shí)際上是不同的變量,但是因?yàn)槲覀兤綍r(shí)在Block內(nèi)部使用的都是對(duì)象键畴,而B(niǎo)lock內(nèi)部對(duì)象是通過(guò)strong引用的方式來(lái)訪問(wèn)外部變量的最盅,以至于掩蓋了Block會(huì)捕獲外部變量的特性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末起惕,一起剝皮案震驚了整個(gè)濱河市涡贱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惹想,老刑警劉巖问词,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘀粱,居然都是意外死亡戏售,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)草穆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)灌灾,“玉大人,你說(shuō)我怎么就攤上這事悲柱》嫦玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵豌鸡,是天一觀的道長(zhǎng)嘿般。 經(jīng)常有香客問(wèn)我,道長(zhǎng)涯冠,這世上最難降的妖魔是什么炉奴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮蛇更,結(jié)果婚禮上瞻赶,老公的妹妹穿的比我還像新娘赛糟。我一直安慰自己,他們只是感情好砸逊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布璧南。 她就那樣靜靜地躺著,像睡著了一般师逸。 火紅的嫁衣襯著肌膚如雪司倚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天篓像,我揣著相機(jī)與錄音动知,去河邊找鬼。 笑死员辩,一個(gè)胖子當(dāng)著我的面吹牛盒粮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屈暗,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脂男!你這毒婦竟也來(lái)了养叛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宰翅,失蹤者是張志新(化名)和其女友劉穎弃甥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體汁讼,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淆攻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘿架。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓶珊。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耸彪,靈堂內(nèi)的尸體忽然破棺而出伞芹,到底是詐尸還是另有隱情,我是刑警寧澤蝉娜,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布唱较,位于F島的核電站,受9級(jí)特大地震影響召川,放射性物質(zhì)發(fā)生泄漏南缓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一荧呐、第九天 我趴在偏房一處隱蔽的房頂上張望汉形。 院中可真熱鬧纸镊,春花似錦、人聲如沸获雕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)届案。三九已至庵楷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間楣颠,已是汗流浹背尽纽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留童漩,地道東北人弄贿。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像矫膨,于是被迫代替她去往敵國(guó)和親差凹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359