iOS 循環參考解決方案,ios引用解決方案
一、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 // 解除循環參考方法119 // __weak 是 iOS 5.0 推出的20 // 如果非同步作業沒有完成,釋放控制器,__weak 本身是弱引用21 // 當非同步執行完畢,進行回調,self 已經被釋放,無法訪問屬性,也無法調用方法22 // __weak 相當於 weak,不會做強引用,但是如果對象被釋放,執行的地址,會指向 nil23 // __weak 更安全24 __weak typeof(self) weakSelf = self;25 26 // 解除循環參考方法227 // __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 end12 13 implementation Friend14 15 -(id)init16 {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)sender25 {26 NSLog(@"%@ say: Hi!", [self class]);27 }28 29 - (void)cleanTimer30 {31 [_timer invalidate];32 _timer = nil;33 }34 35 - (void)dealloc36 {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的記憶體部分就肯定無法再釋放再重新利用這部分記憶體空間了,導致記憶體流失。情況一:delegateDelegate是ios中開發中最常遇到的循環參考,一般在聲明delegate的時候都要使用弱引用weak或者assign@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;當然怎麼選擇使用assign還是weak,MRC的話只能用assign,在ARC的情況下最好使用weak,因為weak修飾的變數在是否後自動為指向nil,防止不安全的野指標存在情況二:BlockBlock也是比較常見的循環參考問題,在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];