也談?wù)凮bjectiveC中__block

導(dǎo)語(yǔ)

幾乎每一個(gè)iOS開(kāi)發(fā)者都知道沦童,在block中無(wú)法修改非靜態(tài)局部變量的值,也知道解決方案是用__block來(lái)修飾一下變量。
但是空骚,有沒(méi)有深入地思考挖掘過(guò)呢?比如:

1.為什么block中無(wú)法修改非靜態(tài)局部變量呢擂仍?

第一反應(yīng)是變量是值傳遞到block中的囤屹,故無(wú)法修改。為什么對(duì)待非靜態(tài)局部變量不能像對(duì)待靜態(tài)局部變量那樣逢渔,直接用指針傳遞呢肋坚?說(shuō)到這就不得不說(shuō),靜態(tài)局部變量和非靜態(tài)局部變量的區(qū)別了复局,靜態(tài)變量存在于應(yīng)用程序的整個(gè)生命周期冲簿,而非靜態(tài)局部變量,僅僅是存在于一個(gè)局部的上下文中亿昏。如果block執(zhí)行過(guò)程中其所指向的非靜態(tài)局部變量還沒(méi)有被椔吞蓿回收的話,這樣執(zhí)行是ok角钩,然后絕大多數(shù)情況下吝沫,block都是延后執(zhí)行的,故這樣非常不妥递礼。
在談為什么加__block可以解決此問(wèn)題之前惨险,我們先討論一個(gè)問(wèn)題,為什么需要我們手動(dòng)的去添加__block呢脊髓,編譯器不能默認(rèn)都給加上__block呢辫愉?如果編譯器這么干了,那么block中所用到的非靜態(tài)全局變量在block中都是可以修改的将硝,其實(shí)block就是一個(gè)匿名函數(shù)恭朗,而非靜態(tài)變量相對(duì)于block而言就是外部變量,這就是典型的在函數(shù)內(nèi)修改外部變量依疼,造成了副作用啊痰腮。此外,這么干也是有違非靜態(tài)變量的初衷律罢,造成了極大的混亂膀值。所以,編譯器默認(rèn)都加上__block修飾符是不妥的,只能將這個(gè)決定權(quán)交給開(kāi)發(fā)者自己去決定是加__block還是不加沧踏。

2.加__block后是什么鬼歌逢?

通過(guò)clang 重寫(xiě)源代碼可以發(fā)現(xiàn)用__block修飾后,原來(lái)的變量已經(jīng)被替換成一個(gè)與之相對(duì)應(yīng)的struct變量(新變量)悦冀,比如趋翻,定義一個(gè)
__block NSMutableArray *array = [NSMutableArray new]; 會(huì)變成
__Block_byref_array_1 array = {0,&array, 33554432, size, copyFunc, disposeFunc,[NSMutableArray new] };(經(jīng)刪除修改)

__Block_byref_array_1的結(jié)構(gòu)體如下所示,

struct __Block_byref_array_1 {
 void *__isa;
__Block_byref_array_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *array;
};

通過(guò)分析發(fā)現(xiàn)盒蟆,結(jié)構(gòu)體中有一個(gè)__forwarding指針踏烙,初始化時(shí)此指針指向轉(zhuǎn)換后變量本身;結(jié)構(gòu)體中也有一個(gè)原變量一樣類(lèi)型的變量历等。

同時(shí)讨惩,此后代碼中涉及到原變量的地方,都會(huì)轉(zhuǎn)換成新變量->__forwarding->原變量同類(lèi)型變量寒屯,其實(shí)關(guān)于這一點(diǎn)很少有書(shū)籍或者文章中提及荐捻,如果不能意識(shí)到這一點(diǎn),對(duì)于很多問(wèn)題理解起來(lái)會(huì)覺(jué)得很詫異寡夹!

3.__block為什么可行处面?

通過(guò)上面的分析,如果在block中直接修改變量的值菩掏,它實(shí)質(zhì)上會(huì)轉(zhuǎn)化成新變量->__forwarding->原變量同類(lèi)型變量魂角。 所以最終修改的其實(shí)是結(jié)構(gòu)體中原變量同類(lèi)型變量,而這個(gè)變量明顯已經(jīng)不屬于block的外部變量了智绸,所以是在block中是可以修改的野揪。

此時(shí),分析到這里瞧栗,還是有兩個(gè)疑問(wèn):

  • 這個(gè)新變量也是非靜態(tài)局部變量斯稳,block執(zhí)行的時(shí)候,新變量可能已經(jīng)被椉?郑回收

如果block執(zhí)行時(shí)挣惰,新變量也已經(jīng)被釋放的話,程序是會(huì)crash的殴边,其實(shí)就算用了__block也不能解決這個(gè)問(wèn)題通熄,或者說(shuō)__block 和這種情況似乎也沒(méi)有什么聯(lián)系吧!

日常開(kāi)發(fā)中找都,好像很少遇到這種crash啊廊酣?因?yàn)閷?shí)際開(kāi)發(fā)中遇到的block大多數(shù)都已經(jīng)copy到了堆上面能耻,block在copy的時(shí)候,也會(huì)觸發(fā)這個(gè)__block變量的copy,會(huì)將變量從椣停空間copy 到堆空間饿幅,所以block在執(zhí)行的時(shí)候,使用的是堆空間上相應(yīng)的變量戒职,因而不會(huì)產(chǎn)生crash!

  • __forwarding的作用是啥栗恩?為什么要這么設(shè)計(jì)?
    • __forwarding有什么用? 哪些地方會(huì)涉及到呢洪燥?

      從代碼層面上分析磕秤,如前文,在使用__block變量時(shí)經(jīng)轉(zhuǎn)換后捧韵,其實(shí)都是通過(guò)其__forwarding來(lái)訪問(wèn)的

      從現(xiàn)象結(jié)果來(lái)看市咆,如果在block中修改了__block變量,block外修改亦有效再来,其實(shí)這也是__forwarding的功效

    • 編譯器是怎么用的蒙兰?這樣用有什么好處?
      這個(gè)可以結(jié)合__block變量的copy源碼來(lái)分析:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
    
    //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
    //printf("src dump: %s\n", _Block_byref_dump(src));
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    }
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        //printf("making copy\n");
        // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
        }
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
            // just bits.  Blast 'em using _Block_memmove in case they're __strong
            _Block_memmove(
                           (void *)&copy->byref_keep,
                           (void *)&src->byref_keep,
                           src->size - sizeof(struct Block_byref_header));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}

從源碼中可以清晰的看到各種細(xì)節(jié)芒篷,這里不做過(guò)多解釋?zhuān)⌒枰⒁獾囊稽c(diǎn)就是 src->forwarding = copy;這里將原對(duì)象的forwarding指向了新創(chuàng)建的對(duì)象搜变。很明顯開(kāi)始__block變量是在棧空間针炉,其forwarding指向自身挠他,當(dāng)變量從棧空間copy到堆空間時(shí)糊识,原來(lái)?xiàng)绩社?臻g的變量的forwarding指向了新創(chuàng)建的變量(堆空間上),這其實(shí)就達(dá)到了從Objective C層面改變?cè)兞康男Ч?/em>

  • 不用__forwarding行不行赂苗?

    暫時(shí)沒(méi)有想到好的代替方案愉耙!歡迎補(bǔ)充!可見(jiàn)__forwarding 確實(shí)是整個(gè)方案設(shè)計(jì)的一大亮點(diǎn)拌滋!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朴沿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子败砂,更是在濱河造成了極大的恐慌赌渣,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昌犹,死亡現(xiàn)場(chǎng)離奇詭異坚芜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斜姥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)鸿竖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沧竟,“玉大人,你說(shuō)我怎么就攤上這事缚忧∥虮茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵闪水,是天一觀的道長(zhǎng)糕非。 經(jīng)常有香客問(wèn)我,道長(zhǎng)球榆,這世上最難降的妖魔是什么朽肥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮芜果,結(jié)果婚禮上鞠呈,老公的妹妹穿的比我還像新娘。我一直安慰自己右钾,他們只是感情好蚁吝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著舀射,像睡著了一般窘茁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脆烟,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天山林,我揣著相機(jī)與錄音,去河邊找鬼邢羔。 笑死驼抹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拜鹤。 我是一名探鬼主播框冀,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敏簿!你這毒婦竟也來(lái)了明也?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惯裕,失蹤者是張志新(化名)和其女友劉穎温数,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜻势,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撑刺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了握玛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猜煮。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡次员,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出王带,到底是詐尸還是另有隱情,我是刑警寧澤市殷,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布愕撰,位于F島的核電站,受9級(jí)特大地震影響醋寝,放射性物質(zhì)發(fā)生泄漏搞挣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一音羞、第九天 我趴在偏房一處隱蔽的房頂上張望囱桨。 院中可真熱鬧,春花似錦嗅绰、人聲如沸舍肠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)翠语。三九已至,卻和暖如春财边,著一層夾襖步出監(jiān)牢的瞬間肌括,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工酣难, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谍夭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓憨募,卻偏偏與公主長(zhǎng)得像紧索,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馋嗜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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