iOS中的閉包(一)

轉(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)存泄露


強(qiáng)引用關(guān)系

我們可以消除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();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容