對于Block
的相關(guān)知識隐绵,可以看《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》這本書,寫得非常透徹拙毫。
一依许、Block
是什么?
Block是C語言的擴充功能缀蹄。是帶有自動變量(局部變量)的匿名函數(shù)峭跳。
Block
也是 Objective-C
對象,將Block
當作 Objective-C
對象來看時缺前,該 Block
的類為 _NSConcreteStackBlock
蛀醉。
二、Block
有幾種類型衅码?
3種:
_NSConcreteStackBlock 該類的對象Block設(shè)置在棧上
_NSConcreteGlobalBlock 與全局變量一樣拯刁,設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中
_NSConcreteMallocBlock 該類的對象設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(即堆)中
三、Block主要應(yīng)用場景:
1.對象的屬性逝段;
2.方法的參數(shù)垛玻;
3.方法的返回值;
四奶躯、Block內(nèi)存管理:
在ARC
環(huán)境帚桩,大多數(shù)情況下編譯器會適當?shù)剡M行判斷,會自動生成將Block
從棧上復(fù)制到堆上的代碼嘹黔。
將Block
作為函數(shù)返回值返回時账嚎,編譯器會自動生成復(fù)制到堆上的代碼。但是有些情況需要我們手動生成代碼將Block
從棧上復(fù)制到堆上参淹,使用“copy
實例方法”醉锄。
編譯器不能判斷“自動將Block
從棧上復(fù)制到堆上”的情況:向方法或函數(shù)的參數(shù)傳遞Block
時乏悄。但是如果方法或函數(shù)內(nèi)部適當?shù)貜?fù)制了傳遞過來的參數(shù)浙值,就不必在調(diào)用該方法或函數(shù)前手動復(fù)制了。例如檩小,系統(tǒng)框架的含Block
的API
可不用手動調(diào)用copy
方法復(fù)制开呐。
以下方法中 Block
作為參數(shù),必須調(diào)用 copy
方法规求,否則會導(dǎo)致程序異常退出筐付。
-(id)getBlockArray
{
int val = 10;
//Block變量類型可以直接調(diào)用copy方法。所以說Block其實也是Objective-C對象阻肿。
//不管Block配置在堆瓦戚、棧或者數(shù)據(jù)區(qū)域丛塌,用copy方法復(fù)制都不會引起任何問題较解。
return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk0:%@",@(val));} copy],[^{NSLog(@"blk1:%@",@(val));} copy], nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
//正常執(zhí)行畜疾。
id obj = [self getBlockArray];
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
}
Demo地址:https://github.com/xiaoL0204/StackBlockDemo
如果不調(diào)用copy
方法,會報如下錯誤印衔。這通常是由野指針引起的啡捶,說明Block對象被釋放了。
通過Block
的復(fù)制奸焙,__block
變量也從棧復(fù)制到堆瞎暑。此時可同時訪問棧上的__block
變量和堆上和__block
變量。
五与帆、什么時候棧上的Block
會復(fù)制到堆呢了赌?
1.調(diào)用Block
的copy
實例方法時;
2.Block
作為函數(shù)返回值返回時玄糟;
3.將Block
賦值給附有__strong
修飾符id
類型的類或Block
類型成員變量時揍拆;
4.在方法名中含有usingBlock
的Cocoa
框架方法或Grand Central Dispatch
的API
中傳遞Block
時。
這些情況下茶凳,可歸結(jié)為_Block_copy
函數(shù)被調(diào)用時Block
從棧復(fù)制到堆嫂拴。釋放Block
時調(diào)用其dispose
函數(shù),相當于對象的dealloc
實例方法贮喧。
六筒狠、Block
特性:截獲對象,對象可超出其變量作用域而存在:
Block中使用的賦值給附有__strong
修飾符的自動變量的對象和復(fù)制到堆上的__block
變量由于被堆上的Block
所持有箱沦,因而可超出其變量作用域而存在辩恼。
如果不調(diào)用Block
的copy
實例方法,Block
不會調(diào)用_Block_copy
函數(shù)谓形,即使截獲了對象灶伊,它也會隨著變量作用域的結(jié)束而被釋放。
超出作用域截獲對象示例:
//Block截獲對象寒跳,對象超出變量的作用域而存在聘萨。
id array = [NSMutableArray array];
void (^blk_t2) (id obj) = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %@,obj:%@",@([array count]),obj);
} copy];
blk_t2([[NSObject alloc] init]);
blk_t2([[NSObject alloc] init]);
blk_t2([[NSObject alloc] init]);
NSLog(@"array:%@",array);
執(zhí)行結(jié)果:
七、Block
循環(huán)引用
如果在Block
中使用__strong
修飾符的對象類型自動變量童太,那么當Block
從棧復(fù)制到堆時米辐,該對象為Block
持有。當對象持有Block
书释,且該Block
持有該對象時翘贮,會引起循環(huán)引用。
Block
內(nèi)部沒有顯示調(diào)用self也可能引起循環(huán)引用爆惧。
使用__weak
修飾符修飾會相互持有的變量狸页,在Block
內(nèi)部使用該變量即可避免循環(huán)引用。
NSTimer
在作為控制器屬性的時候容易產(chǎn)生循環(huán)引用扯再,這點跟Block
循環(huán)引用很類似芍耘。因為NSTimer
會持有target
對象腹侣,除非NSTimer
被置為nil
或invalidate
或停止,否則timer
會一直持有target
對象齿穗,如果此時target
對象持有這個timer
對象傲隶,就會循環(huán)引用,從而造成內(nèi)存泄露窃页。