iOS block循環(huán)引用詳解及常見(jiàn)誤區(qū)
Block循環(huán)引用
什么情況下block會(huì)造成循環(huán)引用
ARC 情況下 block為了保證代碼塊內(nèi)部對(duì)象不被提前釋放,會(huì)對(duì)block中的對(duì)象進(jìn)行強(qiáng)引用,就相當(dāng)于持有了其中的對(duì)象,而如果此時(shí)block中的對(duì)象又持有了該block,就會(huì)造成循環(huán)引用。
常見(jiàn)誤區(qū)
誤區(qū)一.所有block都會(huì)造成循環(huán)引用
在block中,并不是所有的block都會(huì)循造成環(huán)引用,比如UIView動(dòng)畫block、Masonry添加約束block、AFN網(wǎng)絡(luò)請(qǐng)求回調(diào)block等。
1. UIView動(dòng)畫block不會(huì)造成循環(huán)引用是因?yàn)檫@是類方法,不可能強(qiáng)引用一個(gè)類,所以不會(huì)造成循環(huán)引用。
2. Masonry約束block不會(huì)造成循環(huán)引用是因?yàn)閟elf并沒(méi)有持有block,所以我們使用Masonry的時(shí)候不需要擔(dān)心循環(huán)引用。
Masonry內(nèi)部代碼
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; //這里并不是self.block (self并沒(méi)有持有block 所以不會(huì)引起循環(huán)引用) block(constraintMaker); return [constraintMaker install]; }
3.AFN請(qǐng)求回調(diào)block不會(huì)造成循環(huán)引用是因?yàn)樵趦?nèi)部做了處理。
block先是被AFURLSessionManagerTaskDelegate
對(duì)象持有。而AFURLSessionManagerTaskDelegate
對(duì)象被mutableTaskDelegatesKeyedByTaskIdentifier
字典持有,在block執(zhí)行完成后,mutableTaskDelegatesKeyedByTaskIdentifier
字典會(huì)移除AFURLSessionManagerTaskDelegate
對(duì)象,這樣對(duì)象就被釋放了,所以不會(huì)造成循環(huán)引用。
AFN內(nèi)部代碼
#pragma mark - 添加代理 - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; //block被代理引用 delegate.completionHandler = completionHandler; dataTask.taskDescription = self.taskDescriptionForSessionTasks; //設(shè)置代理 [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; } #pragma mark - 設(shè)置代理 - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { NSParameterAssert(task); NSParameterAssert(delegate); [self.lock lock]; //代理被mutableTaskDelegatesKeyedByTaskIdentifier字典引用 self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [delegate setupProgressForTask:task]; [self addNotificationObserverForTask:task]; [self.lock unlock]; } #pragma mark - 任務(wù)完成 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; if (delegate) { //任務(wù)完成,移除 [self removeDelegateForTask:dataTask]; [self setDelegate:delegate forTask:downloadTask]; } if (self.dataTaskDidBecomeDownloadTask) { self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); } } #pragma mark - 移除任務(wù)代理 - (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; [self.lock lock]; [delegate cleanUpProgressForTask:task]; [self removeNotificationObserverForTask:task]; //移除 [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; }
誤區(qū)二.block中只有self會(huì)造成循環(huán)引用
在block中并不只是self會(huì)造成循環(huán)引用,用下劃線調(diào)用屬性(如_name)也會(huì)出現(xiàn)循環(huán)引用,效果和使用self是一樣的(內(nèi)部會(huì)用self->name去查找)。
//會(huì)造成循環(huán)引用 _person1 = [[Person alloc] init]; _person2 = [[Person alloc] init]; _person2.name = @"張三"; [_person1 Block:^{ NSLog(@"%@",_person2.name) }];
誤區(qū)三.通過(guò)__weak __typeof(self) weakSelf = self;可以解決所有block造成的循環(huán)引用
大部分情況下,這樣使用是可以解決block循環(huán)引用,但是有些情況下這樣使用會(huì)造成一些問(wèn)題,比如在block中延遲執(zhí)行一些代碼,在還沒(méi)有執(zhí)行的時(shí)候,控制器被銷毀了,這樣控制器中的對(duì)象也會(huì)被釋放,__weak對(duì)象就會(huì)變成null。所以會(huì)輸出null。
//在延遲執(zhí)行期間,控制器被釋放了,打印出來(lái)的會(huì)是**(null)** _person1 = [[Person alloc] init]; _person2 = [[Person alloc] init]; _person2.name = @"張三"; __weak __typeof(self) weakSelf = self; [_person1 Block:^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",weakSelf.person2.name); }); }];
誤區(qū)四.用self調(diào)用帶有block的方法會(huì)引起循環(huán)引用
并不是所有通過(guò)self調(diào)用帶有block的方法會(huì)引起循環(huán)引用,需要看方法內(nèi)部有沒(méi)有持有self。
//不會(huì)引起循環(huán)引用 [self dismissViewControllerAnimated:YES completion:^{ NSLog(@"%@",self.string); }];
如何避免循環(huán)引用
方式一、weakSelf、strongSelf結(jié)合使用
使用weakSelf
結(jié)合strongSelf
的情況下,能夠避免循環(huán)引用,也不會(huì)造成提前釋放導(dǎo)致block內(nèi)部代碼無(wú)效。
_person1 = [[Person alloc] init]; _person2 = [[Person alloc] init]; _person2.name = @"張三"; __weak __typeof(self) weakSelf = self; [_person1 Block:^{ __typeof(&*weakSelf) strongSelf = weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",strongSelf.person2.name); }); }];
方式二、block的外部對(duì)象使用week
外部對(duì)象通過(guò)week修飾,使用全局弱指針指向一個(gè)局部強(qiáng)引用對(duì)象,這樣局部變量在超出其作用域后也不會(huì)被銷毀,因?yàn)槭侨踔羔?,所以不?huì)造成循環(huán)引用。
@interface CLViewController () //弱引用指針 @property (nonatomic,weak) Person *person1; @property (nonatomic,strong) Person *person2; @end @implementation CLViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor redColor]; //局部強(qiáng)引用對(duì)象 Person *person1 = [[Person alloc] init]; _person1 = person1; _person2 = [[Person alloc] init]; _person2.name = @"張三"; [_person1 Block:^{ NSLog(@"%@",self.person2.name); }]; }
方式三.將對(duì)象置為nil
使用完對(duì)象之后就沒(méi)有必要再保留該對(duì)象了,將對(duì)象置為nil。在ARC中,被置為nil的對(duì)象會(huì)被銷毀。雖然這樣也可以達(dá)到破除循環(huán)引用的效果,但是這樣使用起來(lái)很不方便,如果一個(gè)對(duì)象有多個(gè)block,在前面將對(duì)象置空,后面的block就不會(huì)執(zhí)行,所以使用這種方法來(lái)防止循環(huán)引用,需要注意在合適的位置將對(duì)象置空。
_person1 = [[Person alloc] init]; _person2 = [[Person alloc] init]; _person2.name = @"張三"; [_person1 Block:^{ NSLog(@"%@",self.person2.name); //置空,避免循環(huán)引用 _person1 = nil; }]; //由于上面已經(jīng)將對(duì)象置空,所以這里block里邊的代碼不會(huì)執(zhí)行 [_person1 Block:^{ NSLog(@"%@",self.person2.name); }];
雖然這種方式使用起來(lái)不是很友好,但是在封裝block的時(shí)候,可以考慮使用完馬上置空當(dāng)前使用的block,這樣使用的時(shí)候就不需要考慮循環(huán)引用的問(wèn)題。
- (void)back { if (self.BackBlock) { self.BackBlock(button); } //使用完,馬上置空當(dāng)前block self.BackBlock = nil; }
總結(jié)
使用block的時(shí)候,我們首先需要做的就是判斷當(dāng)前block是否會(huì)引起循環(huán)引用,如果會(huì)引起循環(huán)引用,再考慮采取哪種方式來(lái)避免循環(huán)引用。
到此這篇關(guān)于iOS block循環(huán)引用詳解和應(yīng)用的文章就介紹到這了,更多相關(guān)iOS block循環(huán)引用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
iOS開發(fā)中一些手寫控件及其相關(guān)屬性的使用
這篇文章主要介紹了iOS開發(fā)中一些手寫控件及其相關(guān)屬性的使用,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12IOS 應(yīng)用程序管理的實(shí)現(xiàn)
這篇文章主要介紹了IOS 應(yīng)用程序管理的實(shí)現(xiàn)的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10iOS中使用NSProgress類來(lái)創(chuàng)建UI進(jìn)度條的方法詳解
NSProgress是iOS7以后引入的用于制作進(jìn)度條的類,能夠監(jiān)聽多個(gè)任務(wù),這里就為大家?guī)?lái)iOS中使用NSProgress類來(lái)創(chuàng)建UI進(jìn)度條的方法詳解,需要的朋友可以參考下2016-06-06iOS實(shí)現(xiàn)相冊(cè)和網(wǎng)絡(luò)圖片的存取
本篇文章主要介紹了iOS實(shí)現(xiàn)相冊(cè)和網(wǎng)絡(luò)圖片的存取,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04詳解IOS 利用storyboard修改UITextField的placeholder文字顏色
這篇文章主要介紹了詳解IOS 利用storyboard修改UITextField的placeholder文字顏色的相關(guān)資料,希望通過(guò)本文能實(shí)現(xiàn)這樣類似的功能,需要的朋友可以參考下2017-08-08iOS中利用UIBezierPath + CAAnimation實(shí)現(xiàn)心跳動(dòng)畫效果
這篇文章主要給大家介紹了關(guān)于iOS中利用UIBezierPath + CAAnimation實(shí)現(xiàn)心跳動(dòng)畫效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的日常開發(fā)具有一定的參考學(xué)習(xí),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10