Block循環引用的三種解決方式

今天 抽空看了下 *Objective-C高級編程iOS與OSX多線程和內存管理*,發現本身以前所理解的爲何block會發生循環引用?`有些理解是錯誤的,還好看了這個書,最後弄清楚了,但願寫出來,既能算是一種總結,又能讓其餘小夥伴避免再遇到這個坑!下面 讓咱們一塊兒來看幾個場景!編程

項目簡單的類結構bash

import "ViewController.h"
#import "DetailViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    DetailViewController *detailVC = [DetailViewController new];
    detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
        NSLog(@"d: %@--", d);
    };
    [self presentViewController:d animated:YES completion:nil];
}

//---------------------DetailViewController-----------------------------
@interface DetailViewController : UIViewController

@property (nonatomic, copy) void(^testMemoryLeaksBLock)(DetailViewController *detailVC);

@end

@implementation DetailViewController

-(void)dealloc {
    
    NSLog(@"dealloc");
}

- (void)viewDidDisappear:(BOOL)animated {
    
    [super viewDidDisappear:animated];
    !_testMemoryLeaksBLock ?: _testMemoryLeaksBLock(self);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self dismissViewControllerAnimated:YES completion:nil];
}

複製代碼

場景一

正如項目結構那樣,當detailVC disappear時候,回調block是否會有內存泄漏呢?多線程

detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
        NSLog(@"d: %@--", d);
    };
複製代碼

局部變量不會形成,執行完自動釋放,不會形成內存泄漏 - 1

sow 正如上面分析圖,通常狀況下,是有內存泄漏的,但就是因爲detailVC.testMemoryLeaksBLock所持有的d指針是一個局部變量,當block執行完以後,這個局部變量引用計數就爲0,就被釋放了,所以d 就再也不持有detailVCdetailVC.testMemoryLeaksBLockdetailVC就再也不持有, 循環引用被打破,仍是會走 -[DetailViewController dealloc] 的.app

局部變量不會形成,執行完自動釋放,不會形成內存泄漏 - 2
####更正:block不會強引用 block內部的 局部變量weak弱指針,只會強引用 block 外部 strong指針,並非 block結束以後就會釋放掉局部變量,因此不會引發循環,由於若是像那樣說的話,假如block不執行,那局部變量豈不是就釋放不掉了。 具體看一下例1:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *student = [[Student alloc]init];
    student.name = @"Hello World";
    
    __weak typeof(student) weakStu = student;
    Student *strongStu = weakStu;
    student.study = ^{
        
        //第1種寫法
        //Student *strongStu = weakStu;
        //第2種寫法
        //__strong typeof(weakStu) strongStu = weakStu;
        //第3種寫法
        //typeof(student) strongStu = weakStu;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"my name is = %@", strongStu.name);
        });
    };
}
複製代碼

例1是有內存泄漏的沒有走-[Student dealloc],由於未執行student.study, 因此dispatch_after block也不會走,可是dispatch_after bLock卻強引用了strongStu仍是發生了循環引用。這個比較好理解。可是下面例2,就改了一行代碼 我怎麼也想不通爲何 沒有發生循環引用,走了-[Student dealloc] .ui

例2:atom

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *student = [[Student alloc]init];
    student.name = @"Hello World";
    
    __weak typeof(student) weakStu = student;
    student.study = ^{

        /**
         * 三種寫法是同樣的效果,都是爲了防止局部變量`student`在`viewDidLoad `以後銷燬。若是不這樣寫的話,
         * 因爲`student`局部變量被銷燬,因此爲nil,再走到`dispatch_after block`時候,因爲weakStu是弱指針,
         * 因此不會強引用,最後打印爲null,這不是咱們想要的效果,`AFNetWorking`好多第三方庫也是這麼寫的
         * 第1種寫法
         * Student *strongStu = weakStu;
         * 第2種寫法
         * __strong typeof(weakStu) strongStu = weakStu;
         * 第3種寫法
         * typeof(student) strongStu = weakStu;
         */
        //隨便取哪種寫法,這裏取第一種寫法
         Student *strongStu = weakStu;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"my name is = %@", strongStu.name);
        });
    };
}
複製代碼

dispatch_after block 強引用了外部變量strongStu ,這種不調用student.study()的寫法爲何沒有循環引用呢,可是若是在ViwDidLoad結尾調用student.study(),那麼會在2秒後執行完dispatch_after block纔會走-[Student dealloc],不就說明dispatch_after block持有這個student,走完纔回銷燬,那若是不執行student.study()的話,按道理講,應該也會被dispatch_after block持有這個student,爲何 不會產生循環引用呢。spa

匪夷所思若是有哪一個大神路過,麻煩給個思路,我真想不明白。線程

場景二

block代碼改爲下面這樣,當detailVC disappear時候,回調block是否又會有內存泄漏呢?3d

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
 detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
     NSLog(@"d: %@--\nd: %@",  d,  tmp); 
   };
複製代碼

答案:的確有內存泄漏 ,由於循環引用的問題,具體看下圖:指針

循環引用形成內存泄漏

上面狀況若是懂的話,下面這種寫法是同樣的,經典的循環引用 detailVC持有testMemoryLeaksBLocktestMemoryLeaksBLock 持有 detailVC,致使兩個引用計數都沒法減一,最後誰也釋放不了誰!!

DetailViewController * detailVC = [DetailViewController new]; 
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
     NSLog(@"detailVC: %@",  detailVC); 
   };
複製代碼

##如何解決內存泄漏 緣由咱們瞭解了,如今是該如何解決了,下面根據以前場景逐一給出不一樣的思路,注意思路很重要,由於有不少種解決思路,都要搞清楚,觸類旁通,由於場景一沒有內存泄漏,所以主要針對場景二 ###針對場景二的解決方案1: 有問題的代碼:

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

    NSLog(@"tmp: %@",  tmp); 
  };
複製代碼

方案一的代碼:

DetailViewController * detailVC = [DetailViewController new];
 __block id tmp = detailVC;
 detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

     NSLog(@"tmp: %@",  tmp); 
     tmp = nil;
 };
複製代碼

雖然解決了內存泄漏,可是細心的看客姥爺確定發現了這樣寫的一個弊端,沒錯,那就是 若是 detailVC.testMemoryLeaksBLock ()沒有調用的話,仍是會形成內存泄漏的,由於testMemoryLeaksBLock仍是間接地強引用了detailVC, 算是一個思路吧,畢竟思路要廣才能 想的更多,學的更多,不是嗎!

###針對場景二的解決方案2: 有問題的代碼:

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

    NSLog(@"tmp: %@",  tmp); 
  };
複製代碼

方案二的代碼:

DetailViewController * detailVC = [DetailViewController new];
__weak id weakTmp = detailVC;
 detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

     NSLog(@"weakTmp: %@",  weakTmp); 
 };
複製代碼

這也是日常開發中用得最多的一種解決循環引用的方法。來給小總結吧:方案一和方案二都是斷掉testMemoryLeaksBLockdetailVC的強引用,天然能夠;其實開發中還有一種方案也是能夠的,那就是 斷掉detailVCtestMemoryLeaksBLock 的強引用。

###針對場景二的解決方案3: 有問題的代碼:

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

    NSLog(@"tmp: %@",  tmp); 
  };
複製代碼

方案三的代碼:

@implementation DetailViewController

-(void)dealloc {
    
    NSLog(@"dealloc");
}

- (void)viewDidDisappear:(BOOL)animated {
    
    [super viewDidDisappear:animated];
    !_ testMemoryLeaksBLock ?: _testMemoryLeaksBLock(self);
    //就加了下面一行代碼也是能夠的,由於一旦手動把 _testMemoryLeaksBLock置爲空,  那麼這個block就沒有任何對象持有它,
    //換一句話說就是沒有對象強引用這個block, 那麼若是這個block以前在堆裏,它就會被廢棄掉,
    _testMemoryLeaksBLock= nil;
}
@end
複製代碼

每一次執行完block以後都手動置nil,斷掉detailVCtestMemoryLeaksBLock 的強引用也不失爲一種方法。

####更正:block不會強引用 block內部的局部變量和 弱指針,只會強引用 block 外部strong指針,並非 block結束以後就會釋放掉局部變量,因此不會引發循環,由於若是像那樣說的話,假如block不執行,那局部變量豈不是就釋放不掉了。 ####總結:這也是日常開發中三種解決循環引用的方法。但願你們週末都玩得開心,麼麼噠,下週準備寫 GCD,有好多 線程同步的 知識,望 共同努力,共勉,手動!

相關文章
相關標籤/搜索