開(kāi)始之前,我想先提幾個(gè)問(wèn)題,看看大家是否對(duì)此有疑惑。唐巧已經(jīng)寫過(guò)一篇對(duì)block很有研究的文章,大家可以去看看(本文會(huì)部分引用巧哥文中出現(xiàn)的圖和代碼)。在巧哥的基礎(chǔ)上,我補(bǔ)充一些block相關(guān)的知識(shí)點(diǎn)和代碼,并且概括并修正一些觀點(diǎn)。
1.block是什么?block是對(duì)象嗎?
2.block分為哪幾種?__blcok關(guān)鍵字的作用?
3.block在ARC和MRC下的區(qū)別?
4.block的生命周期?
5.block對(duì)于以參數(shù)形式傳進(jìn)來(lái)的對(duì)象,會(huì)不會(huì)強(qiáng)引用??
block是什么?block是對(duì)象嗎?
先介紹一下什么是閉包。在 wikipedia 上,閉包的定義是:
In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.
翻譯過(guò)來(lái),閉包是一個(gè)函數(shù)(或指向函數(shù)的指針),再加上該函數(shù)執(zhí)行的外部的上下文變量(有時(shí)候也稱作自由變量)。
block 實(shí)際上就是 Objective-C 語(yǔ)言對(duì)于閉包的實(shí)現(xiàn)。
block是不是對(duì)象?答案顯而易見(jiàn):是的。
下圖是block的數(shù)據(jù)結(jié)構(gòu)定義,顯而易見(jiàn),在Block_layout里,我們看到了isa指針,這里我們不具體對(duì)isa指針展開(kāi),也不對(duì)block具體數(shù)據(jù)結(jié)構(gòu)展開(kāi),想了解詳細(xì)可以看唐巧的文章。
回到上文,為什么說(shuō)block是對(duì)象呢,原因就在于isa指針。那么這個(gè)isa指針是何物呢?
所有對(duì)象的都有isa 指針,用于實(shí)現(xiàn)對(duì)象相關(guān)的功能。
看到這,你應(yīng)該明白,block其實(shí)就是objc對(duì)于閉包的對(duì)象實(shí)現(xiàn)。

block分為哪幾種?__blcok關(guān)鍵字的作用?
分為三種,即NSConcreteGlobalBlock、NSConcreteStackBlock、NSConcreteMallocBlock。
詳細(xì)剖析這三種block,首先是NSConcreteGlobalBlock:
簡(jiǎn)單地講,如果一個(gè)block中沒(méi)有引用外部變量,就是NSConcreteGlobalBlock。
如下圖所示:

需要注意的是,NSConcreteGlobalBlock是全局的block,在編譯期間就已經(jīng)決定了,如同宏一樣。
什么是NSConcreteStackBlock呢:
可以這么理解,NSConcreteStackBlock就是引用了外部變量的block,上代碼:

NSConcreteStackBlock不會(huì)持有外部對(duì)象
從打印的日志可以看出,引用計(jì)數(shù)始終沒(méi)變。
NSConcreteMallocBlock:
看似最為神秘的NSConcreteMallocBlock其實(shí)就是一個(gè)block被copy時(shí),將生成NSConcreteMallocBlock(block沒(méi)有retain)。怎么樣,是不是很簡(jiǎn)單

NSConcreteMallocBlock
需要注意的是,NSConcreteMallocBlock會(huì)持有外部對(duì)象!

通過(guò)調(diào)用Block_copy()方法或者直接向他發(fā)送OC的copy消息完成。這就是所謂的Block_copy()。
看到了吧,只要這個(gè)NSConcreteMallocBlock存在,內(nèi)部對(duì)象的引用計(jì)數(shù)就會(huì)+1。
Block_copy()
首先我們來(lái)看Block.h。其中有下面的定義:
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
void *_Block_copy(const void *arg);
所以Block_copy是一個(gè)宏,它將傳入的參數(shù)轉(zhuǎn)換為一個(gè)const void *然后傳遞給_Block_copy()方法。_Block_copy()的實(shí)現(xiàn)在runtime.c:
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
所以也就是調(diào)用_Block_copy_internal方法,傳入block自己和WANTS_ONE。為了明白這什么意思,我們需要看一下實(shí)現(xiàn)代碼。也在runtime.c。下面是方法的實(shí)現(xiàn),已經(jīng)刪掉不想干的部分(主要是垃圾收集的部分):
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
主要做了以下工作:
如果傳入?yún)?shù)是
NULL就直接返回NULL。防止傳入一個(gè)NULL的Block。將參數(shù)轉(zhuǎn)換為一個(gè)
struct Block_layout類型的指針。你也許還記得第一篇文章中提到它。它就是block內(nèi)部一個(gè)包含了實(shí)現(xiàn)函數(shù)和一些元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。如果block的
flags字段包含BLOCK_NEEDS_FREE,那么這是一個(gè)堆block(稍后你就明白)。這里只需要增加引用計(jì)數(shù)然后返回原blcok。如果這是一個(gè)全局block(回看第一篇文章),那么不需要做任何事,直接返回原block。因?yàn)槿謆lock是一個(gè)單例。
如果走到這里,那么這一定是一個(gè)棧上分配的block。那樣的話,block需要拷貝到堆上。這才是有趣的部分。第一步,調(diào)用
malloc()創(chuàng)建一塊特定的內(nèi)存。如果創(chuàng)建失敗,返回NULL;否則,繼續(xù)。調(diào)用
memmove()方法將當(dāng)前棧上分配的block按位拷貝到我們剛剛創(chuàng)建的堆內(nèi)存上。這樣可以保證所有的元數(shù)據(jù)都拷貝過(guò)來(lái),比如descriptor。接下來(lái),更新標(biāo)志位。第一行確保引用計(jì)數(shù)為0。注釋表明這行其實(shí)不需要——大概這個(gè)時(shí)候引用計(jì)數(shù)已經(jīng)是0了。我猜保留這行是因?yàn)橐郧坝袀€(gè)bug導(dǎo)致這里的引用計(jì)數(shù)不是0(所以說(shuō)runtime的代碼也會(huì)偷懶)。下一行設(shè)置了
BLOCK_NEEDS_FREE標(biāo)志位,表明這是一個(gè)堆block,一旦引用計(jì)數(shù)減為0,它所占用的內(nèi)存將被釋放。|1操作設(shè)置block的引用計(jì)數(shù)為1。block的
isa指針被設(shè)置為_NSConcreteMallocBlock,說(shuō)明這是個(gè)堆block。最后,如果block有一個(gè)拷貝輔助函數(shù),那么它將被調(diào)用。必要的時(shí)候編譯器會(huì)生成拷貝輔助函數(shù)。比如一個(gè)捕獲了對(duì)象的block就需要。那么拷貝輔助函數(shù)將持有被捕獲的對(duì)象。
哈哈,已經(jīng)十分清晰了?,F(xiàn)在你知道拷貝一個(gè)block到底發(fā)生了什么事!但那只是圖片展示的一半內(nèi)容,釋放一個(gè)block又會(huì)怎么樣呢?
Block_release()
Block_copy()圖的另一半是Block_release()。實(shí)際上這又是一個(gè)宏:
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
跟Block_copy()一樣,Block_release()也是轉(zhuǎn)換傳入的參數(shù)然后調(diào)用一個(gè)方法。這一定程度上解放了程序員的雙手,他們不用自己做轉(zhuǎn)換。
我們來(lái)看看_Block_release()的源碼(簡(jiǎn)明起見(jiàn),重新整理了代碼順序,并刪除了垃圾回收相關(guān)的代碼):
// 1
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 2
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
// 3
if (newCount > 0) return;
// 4
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
// 5
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
// 6
else {
printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
}
}
這段代碼做了這些事:
首先,參數(shù)被轉(zhuǎn)換為一個(gè)指向
struct Block_layout的指針。如果傳入NULL,直接返回。標(biāo)志位部分表示引用計(jì)數(shù)減1(之前
Block_copy()中標(biāo)志位操作代表的是引用計(jì)數(shù)置為1)。如果新的引用計(jì)數(shù)值大于0,說(shuō)明有其他東西在引用block,所以block不應(yīng)該被釋放。
否則,如果標(biāo)志位包含
BLOCK_NEEDS_FREE,那么這是一個(gè)堆block而且引用計(jì)數(shù)為0,應(yīng)該被釋放。首先block的處理輔助函數(shù)(dispose helper)被調(diào)用,它是拷貝輔助函數(shù)(copy helper)的反義詞,執(zhí)行相反的操作,比如釋放被捕獲的對(duì)象。最后調(diào)用_Block_deallocator方法釋放block。如果你查找runtime.c你就會(huì)發(fā)現(xiàn)這個(gè)方法最后就是一個(gè)free的函數(shù)指針,釋放malloc分配的內(nèi)存。如果到這一步且lock是全局的,什么也不做。
如果到這一步,一定是發(fā)生了未知狀況,因?yàn)橐粋€(gè)棧block試圖在這里釋放,輸出一行警告。實(shí)際上,你應(yīng)該永遠(yuǎn)不會(huì)走到這一步。
下面來(lái)說(shuō)說(shuō)__block這個(gè)關(guān)鍵字:
先上一個(gè)例子,你們很快就會(huì)明白了

沒(méi)錯(cuò),前文說(shuō)過(guò),block引用外部是以捕獲的形式來(lái)捕捉的,而沒(méi)有聲明__block,則會(huì)將外部變量copy進(jìn)block,若用了__block,則是復(fù)制其引用地址來(lái)實(shí)現(xiàn)訪問(wèn)。這就是為什么聲明了__block,在block內(nèi)部改變就會(huì)對(duì)外有影響的原因了。
注意??!這里需要知道的是,在MRC環(huán)境下,如果沒(méi)有用__block,會(huì)對(duì)外部對(duì)象采用copy的操作,而用了__block則不會(huì)用copy的操作。
上代碼:

哈哈哈,怎么樣,所以從更底層的角度來(lái)說(shuō),在MRC環(huán)境下,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制。而這一點(diǎn)往往是很多新手&老手所不知道的!
而在ARC環(huán)境下,對(duì)于聲明為_(kāi)_block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象,所以要謹(jǐn)防循環(huán)引用的問(wèn)題!
block在ARC和MRC下的區(qū)別?
首先要指正下巧哥博客的觀點(diǎn):
在 ARC 開(kāi)啟的情況下,將只會(huì)有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。
在上面介紹NSConcreteStackBlock的時(shí)候,是在ARC環(huán)境下跑的,而打印出來(lái)的日志明確的顯示出,當(dāng)時(shí)的block類型為NSConcreteStackBlock。
而實(shí)際上,為什么大家普遍會(huì)認(rèn)為ARC下不存在NSConcreteStackBlock呢?
這是因?yàn)楸旧砦覀兂3lock賦值給變量,而ARC下默認(rèn)的賦值操作是strong的,到了block身上自然就成了copy,所以常常打印出來(lái)的block就是NSConcreteMallocBlock了。
so,在ARC下,大部分的應(yīng)用場(chǎng)景下,幾乎可以說(shuō)是全部都為NSConcreteMallocBlock或者是NSConcreteGlobalBlock。那么問(wèn)題來(lái)了,我們知道NSConcreteMallocBlock是會(huì)持有外部變量的,而此時(shí)如果它所持有的外部變量正好又持有它,就會(huì)產(chǎn)生循環(huán)引用的問(wèn)題。
讓我們來(lái)聊聊block的生命周期!
block的生命周期?
談到block生命周期,其實(shí)這是一個(gè)非常嚴(yán)肅的話題,雖然block簡(jiǎn)單易用,老少皆宜,但是一旦使用不慎容易造成“強(qiáng)擼灰飛煙滅”的后果(內(nèi)存泄露)。
ps:接下來(lái)的例子都用ARC來(lái)展示了
首先展示:

不用看了,這個(gè)object永遠(yuǎn)也不會(huì)被釋放,這是一個(gè)很典型的循環(huán)引用情形。object持有了block(讀者可以想象此處為何為NSConcreteMallocBlock,提示:在ARC環(huán)境下),而block又持有了object,于是造成死鎖,object再也不會(huì)被釋放了。此時(shí)機(jī)智的編譯器給了你warning,但是在很多復(fù)雜的情況下,編譯器并不能識(shí)別出循環(huán)引用的場(chǎng)景。而此時(shí)你就需要注意了!
那么,我是如何來(lái)處理block的生命周期相關(guān)問(wèn)題的呢,首先前文提到,block是一個(gè)對(duì)象,既然是一個(gè)對(duì)象,它必然有著和對(duì)象一樣的生命周期即如果沒(méi)有被引用就會(huì)被釋放。
所以block的生命周期歸結(jié)起來(lái)很簡(jiǎn)單,只要看持有block的對(duì)象是不是也被block持有,如果沒(méi)有持有,就不用擔(dān)心循環(huán)引用問(wèn)題了。
但是像上面的情況,如果產(chǎn)生相互持有的情況該腫么辦!
你可以用__weak(ARC)或__block(MRC)來(lái)解決:

看,現(xiàn)在就可以愉快的釋放了。
block對(duì)于以參數(shù)形式傳進(jìn)來(lái)的對(duì)象,會(huì)不會(huì)強(qiáng)引用?
唉,不知不覺(jué)已經(jīng)快半夜2點(diǎn)了,對(duì)于這部分的話,其實(shí)也是閑著蛋疼在想這個(gè)問(wèn)題。
其實(shí)block與函數(shù)和方法一樣,對(duì)于傳進(jìn)來(lái)的參數(shù),并不會(huì)持有
證據(jù)如下

總結(jié):
到這里,對(duì)于block的介紹結(jié)束了。實(shí)際運(yùn)用中其實(shí)不用太關(guān)心這些原理的,只需要正確掌握好block的生命周期就可以靈活地運(yùn)用block了。但是對(duì)于一個(gè)資深開(kāi)發(fā)者來(lái)說(shuō),block的深層次掌握還是必須的!
一、整體介紹
- 定義:C語(yǔ)言的匿名函數(shù),??提前準(zhǔn)備一段代碼,在需要的時(shí)候調(diào)用。
- 底層:是一個(gè)指針結(jié)構(gòu)體,在終端下可以通過(guò)
clang -rewrite-objc 文件名(會(huì)在當(dāng)前目錄生成.cpp文件)指令看看c++代碼,它的實(shí)現(xiàn)底層。
注意:容易造成循環(huán)引用,經(jīng)常是在 block 里面使用了 self.,然后形成強(qiáng)引用,我們打斷循 環(huán)鏈即可,如果 MRC 下用__block,ARC 下用__weak(下文會(huì)有詳細(xì)介紹)。
二、內(nèi)存位置(ARC情況)
block塊的存儲(chǔ)位置(block塊入口地址):可能存放在2個(gè)地方:代碼區(qū)(NSConcreteGlobalBlock)、堆區(qū)(NSConcreteMallocBlock),程序分5個(gè)區(qū),還有常量區(qū)、全局區(qū)和棧區(qū),對(duì)于MRC情況下代碼還可能存在棧區(qū)(NSConcreteStackBlock)。關(guān)于內(nèi)存分區(qū)詳細(xì)參考:http://www.itdecent.cn/p/d85a5e56c505
- 情況1:代碼區(qū)
不訪問(wèn)處于棧區(qū)的變量(例如局部變量),且不訪問(wèn)處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象)。也就是說(shuō)訪問(wèn)全局變量也可以。
/**
沒(méi)有訪問(wèn)任何變量
*/
int main(int argc, char * argv[]) { void (^block)(void) = ^{
NSLog(@"===");
};
block();
}
/**
訪問(wèn)了全局(靜態(tài))變量
*/
int iVar = 10; int main(int argc, char * argv[]) { void (^block)(void) = ^{
NSLog(@"===%d",iVar);
};
block();
}
- 情況2:堆區(qū)
如果訪問(wèn)了處于棧區(qū)的變量(例如局部變量),或處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象)。都會(huì)存放在堆區(qū)。(實(shí)際是放在棧區(qū),然后ARC情況下自動(dòng)又拷貝到堆區(qū))
/**
訪問(wèn)局部變量
*/
int main(int argc, char * argv[]) { int iVar = 10; void (^block)(void) = ^{
NSLog(@"===%d",iVar);
};
block();
}
總結(jié)下:
- 代碼區(qū):不訪問(wèn)處于棧區(qū)的變量(例如局部變量),且不訪問(wèn)處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象)。也就是說(shuō)訪問(wèn)全局變量(靜態(tài)變量)也可以,或者是什么變量都不訪問(wèn)
- 堆區(qū):如果訪問(wèn)了處于棧區(qū)的變量(例如局部變量),或處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象),即便也訪問(wèn)了全局變量
三、注意事項(xiàng)
1 block為空
代碼存放在堆區(qū)時(shí),就需要特別注意,因?yàn)槎褏^(qū)不像代碼區(qū)不變化,堆區(qū)是不斷變化的(不斷創(chuàng)建銷毀)。因此代碼有可能會(huì)被銷毀(當(dāng)沒(méi)有強(qiáng)指針指向時(shí)),如果這時(shí)再訪問(wèn)此段代碼則會(huì)程序崩潰。因此,對(duì)于這種情況,我們?cè)诙x一個(gè)block屬性時(shí)應(yīng)指定為strong,或copy:
- @property (nonatomic, strong) void (myBlock)(void); // 這樣就有強(qiáng)指針指向它
- @property (nonatomic, copy) void (myBlock)(void); // 并不會(huì)在堆區(qū)copy一份,原因見(jiàn) 四
而對(duì)于block代碼存在代碼區(qū),使用strong,copy(不會(huì)復(fù)制一份到堆區(qū))也可以。因此定義block時(shí)最好指定為strong(推薦)或copy。我們?cè)谑褂脮r(shí)最后判斷下block是否為空,例如:
- (void)blockTest { // 如果為空則返回
if (!block) {
NSLog(@"block is nil"); return;
}
block();
}
2 當(dāng)不在使用指向block的指針時(shí),將其置空
當(dāng)有類對(duì)象的成員變量pBlock指向block時(shí),一方面是調(diào)用方,調(diào)用pBlock調(diào)用完成后,應(yīng)將pBlock置為nil;另一方面是被調(diào)用方即block函數(shù)內(nèi)部使用到self時(shí)要__weak聲明。其實(shí)__weak聲明有很多注意事項(xiàng),下面是一個(gè)經(jīng)典例子(是正確的寫法):
// 弱聲明,防止block強(qiáng)引用self,造成循環(huán)引用
__weak __typeof(self) weakSelf = self;
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"blockTest" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // 多線程情況下(假設(shè)發(fā)出通知的代碼在另一線程下),strong強(qiáng)引用防止后面調(diào)用strongSelf時(shí):前面的strongSelf正常,后面的strongSelf已在其它線程被釋放,造成很奇怪的結(jié)果,雖然這種情況很少發(fā)生
__strong __typeof(self) strongSelf = weakSelf; //if (strongSelf == nil) { // return; //} // 下面再對(duì)strongSelf進(jìn)行訪問(wèn) // 防止block為空
if (!strongSelf.block) { return;
}
strongSelf.block(); // 如果不用應(yīng)置空,養(yǎng)成好習(xí)慣
strongSelf.block = nil;
NSLog(@"%@",strongSelf);
}];
1)我們都知道在使用通知中心時(shí),應(yīng)在dealloc函數(shù)中釋放通知,如果上面沒(méi)有使用__weak聲明,那么:通知中心持有self.observer,observer又強(qiáng)引用 usingBlock,usingBlock又強(qiáng)引用self,self就不會(huì)被釋放,那么dealloc就不會(huì)被調(diào)用(即使在dealloc中寫了
[[NSNotificationCenter defaultCenter] removeObserver:self.observer]也不會(huì)調(diào)用,因?yàn)閐ealloc沒(méi)有被調(diào)用),就造成內(nèi)存泄露;2)另外,我們?cè)诘?行看到又使用了
__strong聲明,是否瞬間凌亂?下面給出解釋:在多線程情況下,有可能在usingBlock調(diào)用時(shí),執(zhí)行if (!strongSelf.block)時(shí)strongSelf還沒(méi)有釋放,而執(zhí)行到strongSelf.block()的時(shí)候strongSelf就被釋放(現(xiàn)在沒(méi)有強(qiáng)引用了,又開(kāi)始擔(dān)心self被釋放,真是操碎了心。。。),造成調(diào)用失?。ㄗ畲蟮膯?wèn)題是不統(tǒng)一,造成不可預(yù)知的錯(cuò)誤。用__strong操作后保證要么都訪問(wèn)成功,要么都訪問(wèn)失敗或者判斷為空后直接return退出)。
而使用了__strong聲明后:
如果執(zhí)行usingBlock時(shí)self已經(jīng)被釋放則后面的strongSelf均為nil,因?yàn)閷?duì)weakSelf引用計(jì)數(shù)為0再retain一次也不會(huì)有變化;
如果執(zhí)行usingBlock時(shí)self沒(méi)有釋放,則strongSelf會(huì)使self引用計(jì)數(shù)+1,那么self在其它線程被release -1也不會(huì)有影響,只有到usingBlock全部執(zhí)行完畢后,strongSelf釋放,然后self引用計(jì)數(shù)-1,self才會(huì)釋放(weak–strong dance)。
上面的例子是通知中心可能造成的內(nèi)存泄露,而使用block還經(jīng)常出現(xiàn)循環(huán)引用,如下:
3 最常出現(xiàn)的循環(huán)引用
@interface BlockViewController ()
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, copy) NSString *str; @end
@implementation BlockViewController - (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
self.str = @"123";
};
}
@end
上面的代碼,self.block強(qiáng)引用block,而block中又使用了self.str,所以block強(qiáng)引用self,造成強(qiáng)引用,解決方法使用2中所說(shuō)即可。
關(guān)于引用計(jì)數(shù)(http://www.itdecent.cn/p/28b074919df3)
四、關(guān)于捕獲變量
block里面捕獲的變量,都是副本??聪旅嬉欢未a
int val = 10; void (^block)(void) = ^{
NSLog(@"val = %d",val); // val = 1; //不允許
};
val = 5;
block();
它的打印結(jié)果是10,而不是5。
上面代碼中val = 1是不允許的,如果想實(shí)現(xiàn)寫操作,可以使用__block來(lái)修飾val,之后val會(huì)被拷貝(移動(dòng),便于理解)到堆上,之后無(wú)論是在block里面還是在val之前所處的作用域,訪問(wèn)的都是出于堆區(qū)的val。
為什么非要__block呢,因?yàn)槿绻挥胈_block,如果出了val所在的“}”,那么val就會(huì)被釋放,而block的調(diào)用時(shí)機(jī)是不定的,可能調(diào)用時(shí)機(jī)已經(jīng)超出了block和val本身所處的"{}",再訪問(wèn)val就可能壞地址訪問(wèn)(val已經(jīng)被釋放)。所以這樣做是合理的。
但是在block里面,類似self.name = xxx,self->_val,卻是很常見(jiàn)的,self也沒(méi)有用__block修飾呀!你是否有過(guò)這樣的迷惑?
self.name = xxx——>[self setName:xxx];是發(fā)送消息,函數(shù)調(diào)用,很好理解。那self->_val呢?因?yàn)開(kāi)val本身是處于堆區(qū)的。
Block如果沒(méi)有引用外部變量
保存在全局區(qū)(MRC/ARC一樣)
Block如果引用外部變量
ARC保存在 堆區(qū); MRC保存在 棧區(qū)必須用copy修飾block;
1 簡(jiǎn)單理解:block對(duì)應(yīng)實(shí)例化一個(gè)結(jié)構(gòu)體,里面成員有block里用到的變量,準(zhǔn)備了數(shù)據(jù);調(diào)用block時(shí),執(zhí)行對(duì)應(yīng)函數(shù),使用這些數(shù)據(jù);
2 簡(jiǎn)單理解:拷貝進(jìn)來(lái)時(shí),沒(méi)有__block修飾的直接字節(jié)拷貝進(jìn)來(lái),有__block修飾的,為了引地址進(jìn)來(lái),又定義一個(gè)結(jié)構(gòu)體包了一層;
3 block拷貝時(shí),棧到堆上真拷貝,堆到堆上只是引用計(jì)數(shù);
4 block拷貝時(shí),block使用的變量同樣拷貝,拷貝的原則和上面說(shuō)的有無(wú)__block修飾時(shí)的兩種情況分別一致;
5 block里使用外部OC對(duì)象時(shí),本身就相當(dāng)于一個(gè)賦值拷貝,ARC下就會(huì)給對(duì)象加引用次數(shù),所以才有了循環(huán)引用的事;
6 ARC下,等號(hào)賦值,retain、strong、copy都會(huì)觸發(fā)block拷貝到堆上;非ARC有些不一樣;
7 ARC下,assign不會(huì)把棧上block拷貝到堆上,retain、strong、copy都會(huì);非ARC有些不一樣;
8 __block修飾的變量被拷貝到堆上后,__forwarding指向的包裝結(jié)構(gòu)體都是堆上那個(gè),這樣無(wú)論操作棧上還是堆上的包裝結(jié)構(gòu)體,實(shí)際改變值都會(huì)是堆上那個(gè)里的;
9 一些有block參數(shù)的系統(tǒng)API都有說(shuō)明,當(dāng)把棧上block作為參數(shù)給這些API時(shí)會(huì)不會(huì)拷貝block,一般都會(huì),所以才沒(méi)問(wèn)題;
鏈接:
http://www.itdecent.cn/p/e03292674e60
http://www.itdecent.cn/p/93f96c6aa530
http://www.cnblogs.com/mddblog/p/4754190.html
https://blog.csdn.net/demondev/article/details/53199785