你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> ARC下用塊(block)的循環引用問題樣例探究

ARC下用塊(block)的循環引用問題樣例探究

編輯:IOS開發基礎

循環引用的原因

眾所周知,ARC下用block會產生循環引用的問題,造成洩露的原因是啥呢?

最簡單的例子,如下面代碼:

[self.teacher requestData:^(NSData *data) {
    self.name = @"case";
}];

此種情況是最常見的循環引用導致的內存洩露了,在這裡,self強引用了teacher, teacher又強引用了一個block,而該block在回調時又調用了self,會導致該block又強引用了self,造成了一個保留環,最終導致self無法釋放。

self -> teacher -> block -> self

一般性的解決方案

__weak typeof(self) weakSelf = self;
    [self.teacher requestData:^(NSData *data) {
        typeof(weakSelf) strongSelf = weakSelf;
       strongSelf.name = @"case";
    }];

通過__weak的修飾,先把self弱引用(默認是強引用,實際上self是有個隱藏的__strong修飾的),然後在block回調裡用weakSelf,這樣就會打破保留環,從而避免了循環引用,如下:

self -> teacher -> block -> weakSelf

PS:一般會在block回調裡再強引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因為__weak修飾的都是存在棧內,可能隨時會被系統釋放,造成後面調用weakSelf時weakSelf可能已經是nil了,後面用weakSelf調用任何代碼都是無效的。

通過demo證明哪些情況下有洩漏

雖然說用block時會產生循環引用,但並不是所有情況下都會有內存洩露的問題,看個demo。

先發demo地址:https://github.com/yuedong56/BlockRetainCycleDemo

  • 進入demo,有個Button,點擊Button push到SecondViewController,

  • SecondViewController中有六種情況的Button,每點一個Button會觸發一個block,

  • 點擊返回,回到首頁,如果執行了dealloc,證明SecondViewController正常釋放,否則,證明內存洩露了。

01.png

下面只貼demo裡的關鍵代碼了,全的代碼請自行下載demo,大家看下面的六種情況,是否會產生內存洩漏。

//情況一
- (void)case1 {
    NSLog(@"case 1 Click");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.name = @"case 1";
    });
}
//情況二
- (void)case2 {
    NSLog(@"case 2 Click");
    __weak typeof(self) weakSelf = self;
    [self.teacher requestData:^(NSData *data) {
        typeof(weakSelf) strongSelf = weakSelf;
       strongSelf.name = @"case 2";
    }];
}
//情況三
- (void)case3 {
    NSLog(@"case 3 Click");
    [self.teacher requestData:^(NSData *data) {
        self.name = @"case 3";
    }];
}
//情況四
- (void)case4 {
    NSLog(@"case 4 Click");
    [self.teacher requestData:^(NSData *data) {
        self.name = @"case 4";
        self.teacher = nil;
    }];
}
//情況五
- (void)case5 {
    NSLog(@"case 5 Click");
    Teacher *t = [[Teacher alloc] init];
    [t requestData:^(NSData *data) {
        self.name = @"case 5";
    }];
}
//情況六
- (void)case6 {
    NSLog(@"case 6 Click");
    [self.teacher callCase6BlackEvent];
    self.teacher.case6Block = ^(NSData *data) {
        self.name = @"case 6";
        //下面兩句代碼任選其一
        self.teacher = nil;
//        self.teacher.case6Block = nil;
    };
}

分析

  • 情況一:執行了dealloc,不洩露,此情況雖然是block,但未形成保留環block -> self

  • 情況二:執行了dealloc,不洩露,此情況就是內存洩漏後的一般處理了 self ->teacher ->block ->strongSelf,後面那個strongSelf和原來的self並沒有直接關系,因為strongSelf是通過weakSelf得來的,而weakSelf又沒有強引用原來的self

  • 情況三:未執行dealloc,內存洩漏,此情況就是最典型的循環引用了,形成保留環無法釋放,self ->teacher ->block ->self

  • 情況四:執行了dealloc,不洩露,雖然也是保留環,但通過最後一句,使self不再強引用teacher,打破了保留環

  • 情況五:執行了dealloc,不洩露,未形成保留環 t ->block ->self

  • 情況六:執行了dealloc,不洩露,最後兩句代碼任選其一即可防止內存洩漏,self.teacher 或者 case6Block 置為空都可以打破 retain cycle

PS: 雖然情況四、情況六的寫法都可以防止內存洩漏,不過為了統一,個人建議最好還是按照普通寫法即情況二的寫法。

大家有什麼問題可以參考demo,以上純屬個人理解,有不正確的地方,希望大家指出,我的新浪微博:http://weibo.com/1905373741/

參考文章:

  • Swift與OC真正去理解Block解決循環引用的技巧

  • 《Effective Objective-C 2.0 編寫高質量iOS與OS X代碼的52個高效方法》第40條

  • 到底什麼時候才需要在ObjC的Block中使用weakSelf/strongSelf

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved