iOS block循環(huán)引用詳解及常見誤區(qū)
Block循環(huán)引用
什么情況下block會造成循環(huán)引用
ARC 情況下 block為了保證代碼塊內(nèi)部對象不被提前釋放,會對block中的對象進行強引用,就相當(dāng)于持有了其中的對象,而如果此時block中的對象又持有了該block,就會造成循環(huán)引用。
常見誤區(qū)
誤區(qū)一.所有block都會造成循環(huán)引用
在block中,并不是所有的block都會循造成環(huán)引用,比如UIView動畫block、Masonry添加約束block、AFN網(wǎng)絡(luò)請求回調(diào)block等。
1. UIView動畫block不會造成循環(huán)引用是因為這是類方法,不可能強引用一個類,所以不會造成循環(huán)引用。
2. Masonry約束block不會造成循環(huán)引用是因為self并沒有持有block,所以我們使用Masonry的時候不需要擔(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并沒有持有block 所以不會引起循環(huán)引用)
block(constraintMaker);
return [constraintMaker install];
}
3.AFN請求回調(diào)block不會造成循環(huán)引用是因為在內(nèi)部做了處理。
block先是被AFURLSessionManagerTaskDelegate對象持有。而AFURLSessionManagerTaskDelegate對象被mutableTaskDelegatesKeyedByTaskIdentifier字典持有,在block執(zhí)行完成后,mutableTaskDelegatesKeyedByTaskIdentifier字典會移除AFURLSessionManagerTaskDelegate對象,這樣對象就被釋放了,所以不會造成循環(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án)引用
在block中并不只是self會造成循環(huán)引用,用下劃線調(diào)用屬性(如_name)也會出現(xiàn)循環(huán)引用,效果和使用self是一樣的(內(nèi)部會用self->name去查找)。
//會造成循環(huán)引用
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"張三";
[_person1 Block:^{
NSLog(@"%@",_person2.name)
}];
誤區(qū)三.通過__weak __typeof(self) weakSelf = self;可以解決所有block造成的循環(huán)引用
大部分情況下,這樣使用是可以解決block循環(huán)引用,但是有些情況下這樣使用會造成一些問題,比如在block中延遲執(zhí)行一些代碼,在還沒有執(zhí)行的時候,控制器被銷毀了,這樣控制器中的對象也會被釋放,__weak對象就會變成null。所以會輸出null。
//在延遲執(zhí)行期間,控制器被釋放了,打印出來的會是**(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án)引用
并不是所有通過self調(diào)用帶有block的方法會引起循環(huán)引用,需要看方法內(nèi)部有沒有持有self。
//不會引起循環(huán)引用
[self dismissViewControllerAnimated:YES completion:^{
NSLog(@"%@",self.string);
}];
如何避免循環(huán)引用
方式一、weakSelf、strongSelf結(jié)合使用
使用weakSelf結(jié)合strongSelf的情況下,能夠避免循環(huán)引用,也不會造成提前釋放導(dǎo)致block內(nèi)部代碼無效。
_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修飾,使用全局弱指針指向一個局部強引用對象,這樣局部變量在超出其作用域后也不會被銷毀,因為是弱指針,所以不會造成循環(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];
//局部強引用對象
Person *person1 = [[Person alloc] init];
_person1 = person1;
_person2 = [[Person alloc] init];
_person2.name = @"張三";
[_person1 Block:^{
NSLog(@"%@",self.person2.name);
}];
}
方式三.將對象置為nil
使用完對象之后就沒有必要再保留該對象了,將對象置為nil。在ARC中,被置為nil的對象會被銷毀。雖然這樣也可以達到破除循環(huán)引用的效果,但是這樣使用起來很不方便,如果一個對象有多個block,在前面將對象置空,后面的block就不會執(zhí)行,所以使用這種方法來防止循環(huán)引用,需要注意在合適的位置將對象置空。
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"張三";
[_person1 Block:^{
NSLog(@"%@",self.person2.name);
//置空,避免循環(huán)引用
_person1 = nil;
}];
//由于上面已經(jīng)將對象置空,所以這里block里邊的代碼不會執(zhí)行
[_person1 Block:^{
NSLog(@"%@",self.person2.name);
}];
雖然這種方式使用起來不是很友好,但是在封裝block的時候,可以考慮使用完馬上置空當(dāng)前使用的block,這樣使用的時候就不需要考慮循環(huán)引用的問題。
- (void)back
{
if (self.BackBlock)
{
self.BackBlock(button);
}
//使用完,馬上置空當(dāng)前block
self.BackBlock = nil;
}
總結(jié)
使用block的時候,我們首先需要做的就是判斷當(dāng)前block是否會引起循環(huán)引用,如果會引起循環(huán)引用,再考慮采取哪種方式來避免循環(huán)引用。
到此這篇關(guān)于iOS block循環(huán)引用詳解和應(yīng)用的文章就介紹到這了,更多相關(guān)iOS block循環(huán)引用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
iOS開發(fā)中一些手寫控件及其相關(guān)屬性的使用
這篇文章主要介紹了iOS開發(fā)中一些手寫控件及其相關(guān)屬性的使用,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12
iOS中使用NSProgress類來創(chuàng)建UI進度條的方法詳解
NSProgress是iOS7以后引入的用于制作進度條的類,能夠監(jiān)聽多個任務(wù),這里就為大家?guī)韎OS中使用NSProgress類來創(chuàng)建UI進度條的方法詳解,需要的朋友可以參考下2016-06-06
iOS實現(xiàn)相冊和網(wǎng)絡(luò)圖片的存取
本篇文章主要介紹了iOS實現(xiàn)相冊和網(wǎng)絡(luò)圖片的存取,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
詳解IOS 利用storyboard修改UITextField的placeholder文字顏色
這篇文章主要介紹了詳解IOS 利用storyboard修改UITextField的placeholder文字顏色的相關(guān)資料,希望通過本文能實現(xiàn)這樣類似的功能,需要的朋友可以參考下2017-08-08
iOS中利用UIBezierPath + CAAnimation實現(xiàn)心跳動畫效果
這篇文章主要給大家介紹了關(guān)于iOS中利用UIBezierPath + CAAnimation實現(xiàn)心跳動畫效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的日常開發(fā)具有一定的參考學(xué)習(xí),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10

