NSArray* getBlockArray()
{
int num = 100;
;
return [[NSArray alloc]initWithObjects:
^ { NSLog(@"this is block0 : %d", num);},
^ { NSLog(@"this is block1 : %d", num);},
^ { NSLog(@"this is block2 : %d", num);},
nil];
}
int main(int argc, const char * argv[])
{
NSArray *array = getBlockArray();
void (^blockObject)(void);
blockObject = [array objectAtIndex:2];
blockObject();
}
程序會報(bào)錯(cuò)--EXC_BAD_ACCESS
通常這可以理解為一個(gè)“野指針”錯(cuò)誤,訪問了內(nèi)存中不該訪問的內(nèi)容。
問題在哪?從“野指針”錯(cuò)誤,我們很直接能想到的就是block對象引用到的地址內(nèi)容已經(jīng)不是我們想要的了,簡單說就是block無效了。可block是對象類型的啊,為什么放在數(shù)組對象中回傳失效了呢,加入NSArray的對象本身就應(yīng)該retain過啊。
1. Block與對象
首先我們先反思幾個(gè)問題:
block到底是不是對象?
如果是對象,和某個(gè)已定義的類的實(shí)例對象在使用上是不是一樣的?
如果不一樣,主要的區(qū)別是什么?
對于第一個(gè)問題,蘋果的ObjectiveC官方文檔中在“Working with Blocks”明確說明:
“ Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. ”
可見, Block是Objective C語言中的對象 。
蘋果在block的文檔中也提過這么一句:
“ As an optimization, block storage starts out on the stack—just like blocks themselves do. ”
Clang的文檔中也有說明:
“ *The initial allocation is done on the stack, *but the runtime provides a Block_copy
function **” (Block_copy在下面我會說)
憑這一點(diǎn),我們就可以回答剩下的兩個(gè)問題。
Block對象與一般的類實(shí)例對象有所不同,一個(gè)主要的區(qū)別就是分配的位置不同,block默認(rèn)在棧上分配,一般類的實(shí)例對象在堆上分配。
而這正是導(dǎo)致本文最初提到的那個(gè)問題發(fā)生的根本原因。Block對象在棧上分配,block的引用指向棧幀內(nèi)存,而當(dāng)方法調(diào)用過后,指針指向的內(nèi)存上寫的是什么數(shù)據(jù)就不確定了。但是到此,retain的疑問還是沒有解開。
我們想一想Objective C引用計(jì)數(shù)的原理,retain是對一個(gè)在堆中分配內(nèi)存的對象的引用計(jì)數(shù)做了增加,執(zhí)行release操作的時(shí)候檢查計(jì)數(shù)是否為1,如果是則釋放堆中內(nèi)存。而對于在棧上分配的block對象,這一點(diǎn)顯然有所不同,如果方法調(diào)用返回,棧幀上的數(shù)據(jù)自然會作廢處理,不像堆上內(nèi)存,需要單獨(dú)release,就算NSArray對block對象本身做了retain也無濟(jì)于事。
其實(shí)在Clang的文檔中,**只定義了兩個(gè)Block類型: _NSConcreteGlobalBlock 和 _NSConcreteStackBlock **。而在Console中的Log我們看到的3個(gè)類型應(yīng)該是處理過的顯示,這些字樣在蘋果的文檔和Clang/LLVM的文檔中實(shí)難找到。通過字面上來看,可以認(rèn)為 _NSConcreteGlobalBlock對應(yīng)于 NSGlobalBlock ,_NSConcreteStackBlock對應(yīng)于 NSStackBlock ,而NSMallocBlock則是另一種情況。(實(shí)際上也正是如此)
NSGlobalBlock,我們只要實(shí)現(xiàn)一個(gè)沒有對周圍變量沒有引用的Block,就會顯示為是它。而如果其中加入了對定義環(huán)境變量的引用,就是NSStackBlock。那么NSMallocBlock又是哪來的呢?malloc一詞其實(shí)大家都熟悉,就是在堆上分配動態(tài)內(nèi)存時(shí)。沒錯(cuò),如果你對一個(gè)NSStackBlock對象使用了Block_copy()或者發(fā)送了copy消息,就會得到NSMallocBlock。這一段中的幾項(xiàng)結(jié)論可從代碼實(shí)驗(yàn)得出。
因此,也就得到了下面對block的使用注意點(diǎn)。
對于Global的Block,我們無需多處理,不需retain和copy,因?yàn)榧词鼓氵@樣做了,似乎也不會有什么兩樣。對于Stack的Block,如果不做任何操作,就會向上面所說,隨棧幀自生自滅。而如果想讓它獲得比stack frame更久,那就調(diào)用Block_copy(),讓它搬家到堆內(nèi)存上。而對于已經(jīng)在堆上的block,也不要指望通過copy進(jìn)行“真正的copy”,因?yàn)槠湟玫降淖兞咳匀粫峭环荩谶@個(gè)意義上看,這里的copy和retain的作用已經(jīng)非常類似。
“The runtime provides a Block_copy
function which, given a block pointer, either copies the underlying block object to the heap, setting its reference count to 1 and returning the new block pointer, or (if the block object is already on the heap) increases its reference count by 1. The paired function is Block_release
, which decreases the reference count by 1 and destroys the object if the count reaches zero and is on the heap. *”
在類中,如果有block對象作為property,可以聲明為copy。
轉(zhuǎn)載地址: http://www.molotang.com/articles/1691.html
ARC 下內(nèi)存泄露的那些點(diǎn)
https://www.zybuluo.com/MicroCai/note/67734