導(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 *)©->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)拌滋!