iOS開發(fā)中磷箕,block的使用還是挺頻繁的尝江,不管是自定義還是系統(tǒng)提供的很多block函數(shù)吠昭。但是抽絲剝繭的事還是發(fā)生在面試中傲隶,面試的時(shí)候block被提問(wèn)的概率還是挺高的饺律。
以下根據(jù)面試題做出對(duì)block部分知識(shí)點(diǎn)的講解,僅代表個(gè)人看法跺株,如果有錯(cuò)誤复濒,請(qǐng)指出脖卖。
看題之前,先了解一下block類型:
分NSConcreteGlobalBlock
NSConcreteStackBlock
NSConcreteMallocBlock分別在全局變量區(qū)巧颈,棧區(qū)畦木,堆區(qū)。
_NSConcreteGlobalBlock類型的block要么是空block砸泛,要么是不訪問(wèn)任何外部變量的block十籍。它既不在棧中,也不在堆中唇礁,我理解為它可能在內(nèi)存的全局區(qū)勾栗。(配置在全局變量上的Block,從變量作用域外也可以通過(guò)指針安全地使用)
舉例:
void(^ blockA)() = ^{
NSLog(@"just a block");
};
_NSConcreteStackBlock類型的block有閉包行為盏筐,也就是有訪問(wèn)外部變量围俘,并且該block只且只有有一次執(zhí)行,因?yàn)闂V械目臻g是可重復(fù)使用的机断,所以當(dāng)棧中的block執(zhí)行一次之后就被清除出棧了楷拳,所以無(wú)法多次使用。
舉例:
下邊這個(gè)例子吏奸,如果在ARC下創(chuàng)建欢揖,blockC會(huì)創(chuàng)建在堆上,如果在MRC 環(huán)境下創(chuàng)建會(huì)創(chuàng)建在棧上奋蔚。
int value = 10;
void(^ blockC)() = ^{
NSLog(@"just a block === %d", value);
};
_NSConcreteMallocBlock類型的block有閉包行為她混,并且該block需要被多次執(zhí)行。當(dāng)需要多次執(zhí)行時(shí)泊碑,就會(huì)把該block從棧中復(fù)制到堆中坤按,供以多次執(zhí)行。
舉例:
void addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
addBlockToArray中的block還在棧區(qū)馒过,exampleB中的block被復(fù)制到了堆區(qū)變成了NSConcreteMallocBlock臭脓。
提問(wèn)的第一個(gè)問(wèn)題如下:
1,下面代碼在按鈕點(diǎn)擊后腹忽,在ARC下會(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);
}
經(jīng)測(cè)試嘹锁,在ARC環(huán)境下是不會(huì)崩潰的,在MRC環(huán)境下因?yàn)樵L問(wèn)已經(jīng)釋放的對(duì)象着裹,程序崩潰领猾。個(gè)人給出的解釋是,在ARC環(huán)境下,創(chuàng)建的blockC 摔竿,blockC是在堆區(qū)面粮,MRC環(huán)境下blockC是在棧區(qū),棧區(qū)在函數(shù)返回以后就銷毀拯坟,再次訪問(wèn)的時(shí)候就會(huì)引起訪問(wèn)已經(jīng)銷毀的對(duì)象但金。
注:此處之前搜到的答案是這樣的:@property(nonatomic, assign) void(^block)(); 在ARC環(huán)境下,不管用assign,copy還是strong來(lái)修飾block都會(huì)被copy到堆區(qū)郁季,所以block不會(huì)因?yàn)楹瘮?shù)的返回而銷毀冷溃。在MRC環(huán)境下必須用copy然后調(diào)用點(diǎn)語(yǔ)法賦值(self.block = blockC),block 就會(huì)從棧區(qū)copy到堆區(qū)梦裂。
但是實(shí)際測(cè)試結(jié)果如下:
int a =0;
self.block=^{
NSLog(@"aaa%d",a);
};
NSLog(@"aaa");
結(jié)果顯示
所以個(gè)人認(rèn)為似枕,上邊block在ARC環(huán)境下沒(méi)有銷毀,是因?yàn)閎lockC在堆區(qū)年柠,而不是說(shuō)ARC環(huán)境下assign修飾的block被copy到了堆區(qū)凿歼。
因此不管在MRC 還是ARC 定義成屬性的block要用copy防止過(guò)早銷毀。
2冗恨,在ARC環(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();
}
經(jīng)過(guò)測(cè)試blockA是在全局變量區(qū),類型是NSConcreteGlobalBlock前邊注意到(配置在全局變量上的Block掀抹,從變量作用域外也可以通過(guò)指針安全地使用)虐拓。
在MRC環(huán)境直接寫報(bào)錯(cuò),需要將 _weak做處理傲武,不做深究梢杭。
注:在第一題中我們發(fā)現(xiàn)慨蓝,直接創(chuàng)建的block是在堆區(qū)的往弓,但是經(jīng)過(guò)__weak修飾后會(huì)放在棧區(qū)笛坦。
下面代碼中為什么可以直接用self?
[UIView animateWithDuration:1 animations:^{
self.view.backgroundColor = [UIColor yellowColor];
}];
下面這段代碼可以用self嗎疟位?為什么瞻润?
- (void)doSomething {
[BlockClass doSomethingUseBlock:^{
NSLog(@"%@", self);
}];
}
關(guān)于第一個(gè)問(wèn)題,我們會(huì)發(fā)現(xiàn)甜刻,在很多情況下绍撞,block中使用self不會(huì)引起循環(huán)引用問(wèn)題,這首先罢吃,我們要搞明白什么是循環(huán)引用,就是當(dāng)前類持有強(qiáng)引用這個(gè)block昭齐,然后在block中又強(qiáng)引用了當(dāng)前類尿招,彼此等待都不能銷毀。但是UIView是一個(gè)類,當(dāng)前控制器不可能強(qiáng)引用一個(gè)類就谜,所以當(dāng)前控制器沒(méi)有強(qiáng)引用這個(gè)block怪蔑,循環(huán)不成立。(第二個(gè)問(wèn)題也就回答了)
此處拓展丧荐,在AFN中也是在block中使用self缆瓣,他是進(jìn)行了特殊處理,原理可以自己去搜一下虹统。系統(tǒng)GCD是在結(jié)束的時(shí)候?qū)?duì)象都進(jìn)行了釋放弓坞。
以下補(bǔ)充知識(shí)點(diǎn):
補(bǔ)充1. 關(guān)于block中變量修改引起的思考。
我們知道block中是不能直接修改外部變量的车荔,必須經(jīng)過(guò)_block修飾渡冻。原因是:block不允許修改的是棧中指針的內(nèi)存地址,__block的作用是將棧中的地址放到堆中,這樣就可以修改了。
補(bǔ)充2. 關(guān)于block中使用weak和strong修飾詞
首先我們知道忧便,為了防止循環(huán)引用族吻,我們會(huì)使用weak來(lái)修飾self,防止產(chǎn)生強(qiáng)引用,但是在很多框架中我們會(huì)發(fā)現(xiàn)珠增,block中還會(huì)有strong修飾詞超歌,這是防止block還在執(zhí)行的時(shí)候,別的地方把self給釋放了蒂教。找一個(gè)別人寫好的例子
第一步:我們自定義一個(gè)類巍举,在該類dealloc方法中加一行打印語(yǔ)句;
@interface SampleObject :NSObject
@end
@implementation SampleObject
- (void)dealloc{
NSLog(@"dealloc %@",[self class]);
}
@end
第二步:實(shí)例化該類悴品,并在block中調(diào)用它禀综;(沒(méi)有加strong修飾符,三秒后釋放該對(duì)象)
SampleObject* sample = [[SampleObject alloc]init];
self->sample= sample;
__weakSampleObject* weaksample = self->sample;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
NSIntegercount =0;
//__strong SampleObject* strongsample = weaksample;
while(count<10) {
count++;
NSLog(@"aaa %@",weaksample);
sleep(1);
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
self->sample=nil;
});
打印結(jié)果如下(沒(méi)有用strong修飾符的打印結(jié)果如下):
結(jié)論是:如果僅僅使用__weak去修飾變量苔严,當(dāng)別處把變量釋放后定枷,block中該變量也會(huì)被釋放掉。
那么好届氢,我們?cè)诎训诙街械姆椒ㄐ薷囊幌虑分希由蟬trong修飾符:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
__strongSampleObject* strongsample = weaksample;
NSIntegercount =0;
while(count<10) {
count++;
NSLog(@"aaa %@",strongsample);
sleep(1);
}
});
打印結(jié)果如下:
結(jié)論是當(dāng)加上修飾符strong時(shí),當(dāng)別處把變量釋放掉退子,但調(diào)用該變量的block如果仍然沒(méi)有執(zhí)行結(jié)束岖妄,那么系統(tǒng)就會(huì)等待block執(zhí)行完成后再釋放,對(duì)該變量在block中的使用起到了保護(hù)作用寂祥。當(dāng)block執(zhí)行結(jié)束后會(huì)自動(dòng)釋放掉荐虐。
繼續(xù)整理補(bǔ)充,丸凭,福扬,腕铸,