先看一道關(guān)于block的面試題:
1、下面代碼在按鈕點(diǎn)擊后浦辨,在A(yíng)RC下會(huì)發(fā)生什么蹬竖,MRC下呢?為什么?
@property(nonatomic, assign) void(^block)();
- (void)viewDidLoad {
[superviewDidLoad];
int value = 10;
void(^blockC)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockC);
_block = blockC;
}
- (IBAction)action:(id)sender {
NSLog(@"%@", _block);
}
解答:arc下打印的是block內(nèi)存地址币厕,mrc下會(huì)crash
原因:block默認(rèn)存放在棧區(qū)列另,assgin修飾非對(duì)象,引用計(jì)數(shù)不變旦装,mrc下按鈕事件后页衙,已經(jīng)銷(xiāo)毀了(nil)
2,在A(yíng)RC環(huán)境下這段代碼為什么不會(huì)崩潰阴绢?
@property(nonatomic, weak) void(^block)();
- (void)viewDidLoad {
[super viewDidLoad];
void(^ __weak blockA)() = ^{
NSLog(@"just a block");
};
_block = blockA;
}
- (IBAction)action:(id)sender {
_block();
}
解答: 在A(yíng)RC下店乐,進(jìn)行_block = blockA;默認(rèn)_block = [blockA copy];
Block詳細(xì)講解
Block作為C語(yǔ)言的擴(kuò)展,并不是高新技術(shù)呻袭,和其他語(yǔ)言的閉包或lambda表達(dá)式是一回事响巢。需要注意的是由于Objective-C在iOS中不支持GC機(jī)制,使用Block必須自己管理內(nèi)存棒妨,而內(nèi)存管理正是使用Block坑最多的地方踪古,錯(cuò)誤的內(nèi)存管理 要么導(dǎo)致return cycle內(nèi)存泄漏要么內(nèi)存被提前釋放導(dǎo)致crash。 Block的使用很像函數(shù)指針券腔,不過(guò)與函數(shù)最大的不同是:Block可以訪(fǎng)問(wèn)函數(shù)以外伏穆、詞法作用域以?xún)?nèi)的外部變量的值。換句話(huà)說(shuō)纷纫,Block不僅 實(shí)現(xiàn)函數(shù)的功能枕扫,還能攜帶函數(shù)的執(zhí)行環(huán)境。
可以這樣理解辱魁,Block其實(shí)包含兩個(gè)部分內(nèi)容
Block執(zhí)行的代碼烟瞧,這是在編譯的時(shí)候已經(jīng)生成好的;
一個(gè)包含Block執(zhí)行時(shí)需要的所有外部變量值
的數(shù)據(jù)結(jié)構(gòu)染簇。 Block將使用到的参滴、作用域附近到的變量的值
建立一份快照拷貝到棧上。
Block與函數(shù)另一個(gè)不同是锻弓,Block類(lèi)似ObjC的對(duì)象砾赔,可以使用自動(dòng)釋放池管理內(nèi)存(但Block并不完全等同于ObjC對(duì)象,后面將詳細(xì)說(shuō)明)青灼。
Block的類(lèi)型與內(nèi)存管理
根據(jù)Block在內(nèi)存中的位置分為三種類(lèi)型NSGlobalBlock暴心,NSStackBlock, NSMallocBlock。
NSGlobalBlock:類(lèi)似函數(shù)杂拨,位于text段专普;
NSStackBlock:位于棧內(nèi)存,函數(shù)返回后Block將無(wú)效弹沽;
NSMallocBlock:位于堆內(nèi)存檀夹。
下面分別介紹一下這三種類(lèi)型的block筋粗,block在arc和非arc的模式下會(huì)有些需要注意的地方:
NSGlobalBlock:在block內(nèi)部沒(méi)有引用任何外部變量
void (^globalBlock) () = ^ () {
NSLog(@"global block");
};
NSLog(@"%@", globalBlock);//<__NSGlobalBlock__: 0x1096e20c0>
對(duì)NSGlobalBlock的retain、copy击胜、release操作都無(wú)效亏狰。
NSStackBlock:在block內(nèi)部引用外部變量
先討論下MRC模式:
int base = 100;
long (^stackBlock) (int, int) = ^ long (int a, int b) {
return base +a + b;
};
NSLog(@"%@",stackBlock);//<__NSStackBlock__: 0x7fff57c6bce0>
棧block在當(dāng)函數(shù)退出的時(shí)候役纹,該空間就會(huì)被回收偶摔,因此如果再調(diào)用該block會(huì)導(dǎo)致crash:
void example_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%cn", b);
}];
}
void example() {
NSMutableArray *array = [NSMutableArray array];
example_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
在example_addBlockToArray函數(shù)中添加的block由于為棧block,因此在example函數(shù)中調(diào)用的話(huà)會(huì)導(dǎo)致程序crash掉促脉,可以通過(guò)將block拷貝到堆上來(lái)解決這個(gè)問(wèn)題:
[array addObject:[[^{
printf("%cn", b);
} copy autorelease]]];
retain辰斋、release這種類(lèi)型的block不起作用。下面看看在A(yíng)RC模式下會(huì)有啥不同的地方:
int base = 100;
long (^stackBlock) (int, int) = ^ long (int a, int b) {
return base +a + b;
};
NSLog(@"%@",stackBlock);//<__NSMallocBlock__: 0x7f8da961e590>
我們發(fā)現(xiàn)在A(yíng)RC模式下瘸味,打印出來(lái)的結(jié)果并不是NSStackBlock這個(gè)類(lèi)型宫仗,很多人以為在A(yíng)RC模式下block的類(lèi)型只有NSGlobalBlock和NSMallocBlock兩種,其實(shí)這種觀(guān)點(diǎn)是錯(cuò)誤的旁仿。在A(yíng)RC情況下藕夫,生成的block也是NSStackBlock,只是當(dāng)賦值給strong對(duì)象時(shí)枯冈,系統(tǒng)會(huì)主動(dòng)對(duì)其進(jìn)行copy:
int i=0;
NSLog(@"%@", ^{
NSLog(@"stack block here, i=%d", i);
});//<__NSStackBlock__: 0x7fff592eacf8>
void (^block)()=^{
NSLog(@"stack block here, i=%d", i);
};
NSLog(@"%@",block);//<__NSMallocBlock__: 0x7fae49e02660>
NSMallocBlock
如果NSStackBlock需要在其作用域外部使用的時(shí)候毅贮,在MRC的模式下需要手動(dòng)將其copy到堆上,NSMallocBlock支持retain尘奏、release滩褥,會(huì)對(duì)其引用計(jì)數(shù)+1或-1,copy不會(huì)生成新的對(duì)象炫加,只是增加了一次引用瑰煎,類(lèi)似retain;而在A(yíng)RC模式下會(huì)自動(dòng)對(duì)其進(jìn)行copy俗孝,不需要自己手動(dòng)去管理酒甸,盡可能使用ARC。