iOS block循環引用詳解及常見誤區
Block循環引用
什麼情況下block會造成循環引用
ARC 情況下 block為瞭保證代碼塊內部對象不被提前釋放,會對block中的對象進行強引用,就相當於持有瞭其中的對象,而如果此時block中的對象又持有瞭該block,就會造成循環引用。
常見誤區
誤區一.所有block都會造成循環引用
在block中,並不是所有的block都會循造成環引用,比如UIView動畫block、Masonry添加約束block、AFN網絡請求回調block等。
1. UIView動畫block不會造成循環引用是因為這是類方法,不可能強引用一個類,所以不會造成循環引用。
2. Masonry約束block不會造成循環引用是因為self並沒有持有block,所以我們使用Masonry的時候不需要擔心循環引用。
Masonry內部代碼
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; //這裡並不是self.block (self並沒有持有block 所以不會引起循環引用) block(constraintMaker); return [constraintMaker install]; }
3.AFN請求回調block不會造成循環引用是因為在內部做瞭處理。
block先是被AFURLSessionManagerTaskDelegate
對象持有。而AFURLSessionManagerTaskDelegate
對象被mutableTaskDelegatesKeyedByTaskIdentifier
字典持有,在block執行完成後,mutableTaskDelegatesKeyedByTaskIdentifier
字典會移除AFURLSessionManagerTaskDelegate
對象,這樣對象就被釋放瞭,所以不會造成循環引用。
AFN內部代碼
#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; //設置代理 [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; } #pragma mark - 設置代理 - (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 - 任務完成 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; if (delegate) { //任務完成,移除 [self removeDelegateForTask:dataTask]; [self setDelegate:delegate forTask:downloadTask]; } if (self.dataTaskDidBecomeDownloadTask) { self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); } } #pragma mark - 移除任務代理 - (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]; }
誤區二.block中隻有self會造成循環引用
在block中並不隻是self會造成循環引用,用下劃線調用屬性(如_name)也會出現循環引用,效果和使用self是一樣的(內部會用self->name去查找)。
//會造成循環引用 _person1 = [[Person alloc] init]; _person2 = [[Person alloc] init]; _person2.name = @"張三"; [_person1 Block:^{ NSLog(@"%@",_person2.name) }];
誤區三.通過__weak __typeof(self) weakSelf = self;可以解決所有block造成的循環引用
大部分情況下,這樣使用是可以解決block循環引用,但是有些情況下這樣使用會造成一些問題,比如在block中延遲執行一些代碼,在還沒有執行的時候,控制器被銷毀瞭,這樣控制器中的對象也會被釋放,__weak對象就會變成null。所以會輸出null。
//在延遲執行期間,控制器被釋放瞭,打印出來的會是**(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); }); }];
誤區四.用self調用帶有block的方法會引起循環引用
並不是所有通過self調用帶有block的方法會引起循環引用,需要看方法內部有沒有持有self。
//不會引起循環引用 [self dismissViewControllerAnimated:YES completion:^{ NSLog(@"%@",self.string); }];
如何避免循環引用
方式一、weakSelf、strongSelf結合使用
使用weakSelf
結合strongSelf
的情況下,能夠避免循環引用,也不會造成提前釋放導致block內部代碼無效。
_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的外部對象使用week
外部對象通過week修飾,使用全局弱指針指向一個局部強引用對象,這樣局部變量在超出其作用域後也不會被銷毀,因為是弱指針,所以不會造成循環引用。
@interface CLViewController () //弱引用指針 @property (nonatomic,weak) Person *person1; @property (nonatomic,strong) Person *person2; @end @implementation CLViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor redColor]; //局部強引用對象 Person *person1 = [[Person alloc] init]; _person1 = person1; _person2 = [[Person alloc] init]; _person2.name = @"張三"; [_person1 Block:^{ NSLog(@"%@",self.person2.name); }]; }
方式三.將對象置為nil
使用完對象之後就沒有必要再保留該對象瞭,將對象置為nil。在ARC中,被置為nil的對象會被銷毀。雖然這樣也可以達到破除循環引用的效果,但是這樣使用起來很不方便,如果一個對象有多個block,在前面將對象置空,後面的block就不會執行,所以使用這種方法來防止循環引用,需要註意在合適的位置將對象置空。
_person1 = [[Person alloc] init]; _person2 = [[Person alloc] init]; _person2.name = @"張三"; [_person1 Block:^{ NSLog(@"%@",self.person2.name); //置空,避免循環引用 _person1 = nil; }]; //由於上面已經將對象置空,所以這裡block裡邊的代碼不會執行 [_person1 Block:^{ NSLog(@"%@",self.person2.name); }];
雖然這種方式使用起來不是很友好,但是在封裝block的時候,可以考慮使用完馬上置空當前使用的block,這樣使用的時候就不需要考慮循環引用的問題。
- (void)back { if (self.BackBlock) { self.BackBlock(button); } //使用完,馬上置空當前block self.BackBlock = nil; }
總結
使用block的時候,我們首先需要做的就是判斷當前block是否會引起循環引用,如果會引起循環引用,再考慮采取哪種方式來避免循環引用。
到此這篇關於iOS block循環引用詳解和應用的文章就介紹到這瞭,更多相關iOS block循環引用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- iOS GCD之dispatch_group_enter和dispatch_group_leave使用
- iOS文件預覽分享小技能示例
- iOS開發之MRC(手動內存管理)詳解
- iOS NSCache和NSUrlCache緩存類實現示例詳解
- Objective-C 入門篇(推薦)