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

今天 抽空看了下 *Objective-C高級編程iOS與OSX多線程和內存管理*,發現自己之前所理解的為什么block會發生循環引用?`有些理解是錯誤的,還好看了這個書,最后弄清楚了,希望寫出來,既能算是一種總結,又能讓其他小伙伴避免再遇到這個坑!下面 讓我們一起來看幾個場景!

項目簡單的類結構

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] 的.

局部變量不會造成,執行完自動釋放,不會造成內存泄漏 - 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] .

例2:

- (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,為什么 不會產生循環引用呢。

匪夷所思如果有哪個大神路過,麻煩給個思路,我真想不明白。

場景二

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

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,有好多 線程同步的 知識,望 共同努力,共勉,手動!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容