一、BLOCK 迴圈引用 一般表現為,某個類將block作為自己的屬性變數,然後該類在block的方法體裡面又使用了該類本身。構成迴圈引用。 // 定義 block 的時候,會對外部變數做一次 copy,強引用, self自身為強引用。 解決方案如下: 二、計時器NSTimer迴圈引用 主要是因為從 ...
一、BLOCK 迴圈引用
一般表現為,某個類將block作為自己的屬性變數,然後該類在block的方法體裡面又使用了該類本身。構成迴圈引用。
// 定義 block 的時候,會對外部變數做一次 copy,強引用, self自身為強引用。
解決方案如下:
1 #import "ViewController.h" 2 #import "NetworkTools.h" 3 4 @interface ViewController () 5 @property (nonatomic, strong) NetworkTools *tools; 6 @end 7 8 @implementation ViewController 9 // 1. 解除迴圈引用,需要註意打斷引用鏈條即可! 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 // 局部變數不會產生迴圈應用,全局屬性會產生迴圈引用 14 self.tools = [[NetworkTools alloc] init]; 15 16 // 1. 定義 block 的時候,會對外部變數做一次 copy,會對 self 進行強引用 17 18 // 解除迴圈引用方法1 19 // __weak 是 iOS 5.0 推出的 20 // 如果非同步操作沒有完成,釋放控制器,__weak 本身是弱引用 21 // 當非同步執行完畢,進行回調,self 已經被釋放,無法訪問屬性,也無法調用方法 22 // __weak 相當於 weak,不會做強引用,但是如果對象被釋放,執行的地址,會指向 nil 23 // __weak 更安全 24 __weak typeof(self) weakSelf = self; 25 26 // 解除迴圈引用方法2 27 // __unsafe_unretained 是 iOS 4.0 推出的 28 // MRC 經典錯誤,EXC_BAD_ACCESS 壞記憶體訪問,野指針 29 // 相當於 assign,不會做強引用,但是如果對象被釋放,記憶體地址保持不變,如果此時再調用,就會出現野指針訪問 30 // __unsafe_unretained typeof(self) weakSelf = self; 31 32 [self.tools loadData:^(NSString *html) { 33 // strongSelf 強引用,對 weakSelf 進行強引用,本意,希望在非同步完成後,繼續執行回調代碼 34 //然而並沒有什麼作用!!!!!!!! 35 __strong typeof(self) strongSelf = weakSelf; 36 37 NSLog(@"%@ %@", html, strongSelf.view); 38 }]; 39 } 40 41 - (void)dealloc { 42 NSLog(@"控制器 88"); 43 } 44 45 @end
二、計時器NSTimer迴圈引用
主要是因為從timer的角度,timer認為調用方self被析構時會進入dealloc,在dealloc可以順便將timer的計時停掉並且釋放記憶體;但是從self的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。迴圈引用,互相等待,子子孫孫無窮盡也。
例子說明:
一方面,NSTimer經常會被作為某個類的成員變數,而NSTimer初始化時要指定self為target,容易造成迴圈引用。 另一方面,若timer一直處於validate的狀態,則其引用計數將始終大於0。先看一段NSTimer使用的例子(ARC模式):
1 import <Foundation/Foundation.h> 2 3 interface Friend : NSObject 4 -(void)cleanTimer; 5 end 6 7 import "Friend.h" 8 9 interface Friend () 10 STimer *_timer; 11 end 12 13 implementation Friend 14 15 -(id)init 16 { 17 if (self = [super init]) { 18 _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) 19 userInfo:nil repeats:YES]; 20 } 21 return self; 22 } 23 24 - (void)handleTimer:(id)sender 25 { 26 NSLog(@"%@ say: Hi!", [self class]); 27 } 28 29 - (void)cleanTimer 30 { 31 [_timer invalidate]; 32 _timer = nil; 33 } 34 35 - (void)dealloc 36 { 37 [self cleanTimer]; 38 NSLog(@"[Friend class] is dealloced"); 39 } 40 41 @end
在類外部初始化一個Friend對象,並延遲5秒後將friend釋放(外部運行在非arc環境下)
1 Friend *f = [[Friend alloc] init]; 2 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 3 [f release]; 4 });
我們所期待的結果是,初始化5秒後,f對象被release,f的dealloc方法被調用,在dealloc裡面timer失效,對象被析構。但結果卻是如此:
1 2015-03-18 18:00:35.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2 2015-03-18 18:00:36.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 3 2015-03-18 18:00:37.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 4 2015-03-18 18:00:38.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 5 2015-03-18 18:00:39.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!//運行了5次後沒按照預想的停下來 6 2015-03-18 18:00:40.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 7 2015-03-18 18:00:41.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 8 2015-03-18 18:00:42.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 9 2015-03-18 18:00:43.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 10 2015-03-18 18:00:44.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!<br>.......根本停不下來.....
這是為什麼呢?主要是因為從timer的角度,timer認為調用方(Friend對象)被析構時會進入dealloc,在dealloc可以順便將timer的計時停掉並且釋放記憶體;但是從Friend的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。迴圈引用,互相等待,子子孫孫無窮盡也。問題的癥結在於-(void)cleanTimer函數的調用時機不對,顯然不能想當然地放在調用者的dealloc中。一個比較好的解決方法是開放這個函數,讓Friend的調用者顯式地調用來清理現場。如下:
1 Friend *f = [[Friend alloc] init]; 2 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 3 [f cleanTimer]; 4 [f release]; 5 });
三、委托delegate
在委托問題上出現迴圈引用問題已經是老生常談了,本文也不再細講,規避該問題的殺手鐧也是簡單到哭,一字訣:聲明delegate時請用assign(MRC)或者weak(ARC),千萬別手賤玩一下retain或者strong,畢竟這基本逃不掉迴圈引用了!
上面說的是我們常見的,其實迴圈引用就是說我們的強引用形成了閉環,還會有很多自己寫的代碼中會出現,平時還是要註意寫法。
不好意思,下麵再啰嗦一遍,進一步說明:
迴圈引用,指的是多個對象相互引用時,使得引用形成一個環形,導致外部無法真正是否掉這塊環形記憶體。其實有點類似死鎖。 舉個例子:A->B->C->....->X->B ->表示強引用,這樣的B的引用計數就是2,假如A被系統釋放了,理論上A會自動減小A所引用的資源,就是B,那麼這時候B的引用計數就變成了1,所有B無法被釋放,然而A已經被釋放了,所有B的記憶體部分就肯定無法再釋放再重新利用這部分記憶體空間了,導致記憶體泄漏。 情況一:delegate Delegate是ios中開發中最常遇到的迴圈引用,一般在聲明delegate的時候都要使用弱引用weak或者assign @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate; 當然怎麼選擇使用assign還是weak,MRC的話只能用assign,在ARC的情況下最好使用weak,因為weak修飾的變數在是否後自動為指向nil,防止不安全的野指針存在 情況二:Block Block也是比較常見的迴圈引用問題,在Block中使用了self容易出現迴圈引用,因此很多人在使用block的時候,加入裡面有用到self的操作都會聲明一個__weak來修飾self。其實便不是這樣的,不是所有使用了Block都會出現Self迴圈引用問題,只有self擁有Block的強引用才會出現這種情況。 所以一般在函數中臨時使用Block是不會出現迴圈應用的,因為這時候Block引用是屬於棧的。當棧上的block釋放後,block中對self的引用計數也會減掉 當然不一定要Self對Block有直接的引用才會出現,假如self的變數B,B中有個Block變數,就容易出現這種情況,好的是在block出現迴圈引用的,xcode7會出現警告提示(之前版本不確定)。 情況三:NSTimer 這是一個神奇的NSTimer,當你創建使用NSTimer的時候,NSTimer會預設對當前self有個強引用,所有在self使用完成打算是否的時候,一定要先使用NSTimer的invalidate來停止是否時間控制對self的引用 [_timer invalidate];