
轉(zhuǎn)載請(qǐng)注明出處:
http://www.itdecent.cn/p/431bddd44cdb
作者:紀(jì)小衰
一、基本用法
1.定義一個(gè)閉包類(lèi)型
typedef void(^Call)(NSUInteger);
2.兩種方式聲明一個(gè)閉包
Call call;
NSUInteger(^sum)(NSUInteger i,NSUInteger j);
3.兩種方式創(chuàng)建一個(gè)閉包
call = ^(NSUInteger i){
NSLog(@"%ld",i);
};
sum = ^(NSUInteger i,NSUInteger j){
return i+j;
};
4.閉包作為函數(shù)參數(shù)
- (void)testBlock:(NSUInteger(^)(NSUInteger i, NSUInteger j))block{
NSLog(@"sum = %ld",block(1,2));
}
二、閉包的強(qiáng)引用
閉包內(nèi)可以直接使用閉包調(diào)用上下文的對(duì)象和方法,并且對(duì)在閉包中使用的對(duì)象進(jìn)行一次強(qiáng)引用。為了驗(yàn)證這點(diǎn),我們可以新建一個(gè)TestView類(lèi),繼承自UIView并重寫(xiě)dealloc方法用來(lái)判斷是否被銷(xiāo)毀:
- (void)dealloc{
NSLog(@"dealloc");
}
新建一個(gè)UIViewController,聲明一個(gè)閉包的屬性,讓UIViewController持有一個(gè)閉包
@interface ViewController : UIViewController
@property (strong, nonatomic) void(^testBlock)() ;
@end
在viewDidload中創(chuàng)建TestView添加到vc的view上,設(shè)置屬性閉包,并且在閉包中輸出view,然后移除TestView
- (void)viewDidLoad {
[super viewDidLoad];
TestView* testview = [[TestView alloc] init];
[self.view addSubview:testview];
[self setTestBlock:^{
NSLog(@"%@",testview);
}];
[testview removeFromSuperview];
}
由于testview是一個(gè)局部變量,當(dāng)我們把testView從添加到VC的view中時(shí),VC的view會(huì)對(duì)testview產(chǎn)生一個(gè)強(qiáng)引用,當(dāng)我們移除以后強(qiáng)引用會(huì)移除,當(dāng)viewDidLoad執(zhí)行完畢以后,應(yīng)該是沒(méi)有強(qiáng)引用了,但是testview的dealloc方法并沒(méi)有被調(diào)用,原因是UIViewController強(qiáng)引用了一個(gè)閉包testBlock,閉包中會(huì)執(zhí)行輸出testview的操作,所以對(duì)于testview也進(jìn)行了強(qiáng)引用,使得testview不能被銷(xiāo)毀,只能在UIViewcontroller銷(xiāo)毀->持有的閉包銷(xiāo)毀->testview強(qiáng)引用變成0才會(huì)真正被銷(xiāo)毀
三、閉包的循環(huán)引用
我們修改viewDidload方法,在閉包中不僅輸出testView,也輸出VC自身的view。重寫(xiě)UIViewController的dealloc方法
- (void)viewDidLoad {
[super viewDidLoad];
TestView* testview = [[TestView alloc] init];
[self.view addSubview:testview];
[self setTestBlock:^{
NSLog(@"%@",testview);
NSLog(@"%@",self.view);//輸出自己的view
}];
[testview removeFromSuperview];
}
- (void)dealloc{
NSLog(@"controller 被銷(xiāo)毀");
}
我們新建一個(gè)新的UIViewcontroller和一個(gè)UINavigationController,設(shè)置navigation的rootViewController為新建的UIViewcontroller,然后push之前的viewController,最后再pop返回
這時(shí)候我們可以發(fā)現(xiàn),現(xiàn)在testView還是沒(méi)有被釋放,而且被pop的UIViewcontroller也沒(méi)有被釋放,此時(shí)被pop的vc和testview已經(jīng)變成了內(nèi)存泄露
原因則是UIViewController強(qiáng)引用了一個(gè)閉包,在閉包中使用了self.view,那么閉包會(huì)對(duì)self也就是UIViewController也產(chǎn)生了一個(gè)強(qiáng)引用,這樣導(dǎo)致了UIViewController和testBlock都不能被釋放,閉包強(qiáng)引用的testeview也不能被釋放了,造成了內(nèi)存泄露

我們可以消除testBlock對(duì)于UIViewController的強(qiáng)引用來(lái)消除循環(huán)引用,在閉包中需要使用對(duì)象而不需要持有,我們可以使用該對(duì)象的弱引用。在本例中testBlock不必要持有UIViewController,因?yàn)楫?dāng)UIViewController被銷(xiāo)毀的時(shí)候,testBlock此后也不會(huì)被執(zhí)行了
- (void)viewDidLoad {
[super viewDidLoad];
TestView* view = [[TestView alloc] init];
[self.view addSubview:view];
typeof(self) weakSelf = self;
[self setTestBlock:^{
NSLog(@"%@",view);
NSLog(@"%@",weakSelf.view);//使用弱引用調(diào)用方法或?qū)傩? }];
[view removeFromSuperview];
注:
不要想著使用_view的方式對(duì)對(duì)象的屬性進(jìn)行直接訪(fǎng)問(wèn)來(lái)消除對(duì)self的強(qiáng)引用。不建議的原因有兩點(diǎn):
1.直接調(diào)用變量代碼層面真心不好看。
2.使用“_變量名”的方式并沒(méi)有消除強(qiáng)引用,在調(diào)用的時(shí)還是會(huì)對(duì)self進(jìn)行一次間接的強(qiáng)引用
四、閉包的判空
方法doSomething需要在執(zhí)行操作完執(zhí)行完成的閉包,例如封裝http請(qǐng)求,請(qǐng)求完成后需要執(zhí)行的操作
- (void)doSomething:(void(^)())finished{
NSLog(@"I am doing something...");
finished();
}
正常方法調(diào)用:
[self doSomething:^{
NSLog(@"finished");
}];
如果我們?cè)谡{(diào)用doSomething的時(shí)候不想傳入任務(wù)完成的閉包可以:
[self doSomething:nil];
實(shí)際上上面的代碼會(huì)發(fā)生EXC_BAD_ACCESS崩潰,原因是傳入的閉包為空。調(diào)用閉包和調(diào)用oc對(duì)象的方法并不同,oc對(duì)象調(diào)用方法是通過(guò)消息發(fā)送的形式,當(dāng)oc對(duì)象為nil則不對(duì)消息做出反應(yīng),但是當(dāng)閉包為空的時(shí)候并不能進(jìn)行調(diào)用。我們可以修改方法doSomething,對(duì)閉包進(jìn)行判空操作:
- (void)doSomething:(void(^)())finished{
NSLog(@"I am doing something...");
if(finished){
finished();
}
}
當(dāng)然,如果閉包會(huì)調(diào)用多次,則會(huì)多次判空,if的過(guò)多使用會(huì)導(dǎo)致代碼的不緊湊,如果你和我一樣是代碼完美主義者,可以使用下面的方法進(jìn)行判空:
- (void)doSomething:(void(^)())finished{
NSLog(@"I am doing something...");
!finished?:finished();
}