ARC已經出來好久了,自動釋放內存的確很方便,可是並不是絕對安全絕對不會產生內存泄露。致使iOS對象沒法按預期釋放的一個無形殺手是——循環引用。循環引用能夠簡單理解爲A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,致使任什麼時候候引用計數都不爲0,始終沒法釋放。若當前對象是一個ViewController,則在dismiss或者pop以後其dealloc沒法被調用,在頻繁的push或者present以後內存暴增,而後APP就duang地掛了。下面列舉咱們變成中比較容易碰到的三種循環引用的情形。html
(1)計時器NSTimerios
一方面,NSTimer常常會被做爲某個類的成員變量,而NSTimer初始化時要指定self爲target,容易形成循環引用。 另外一方面,若timer一直處於validate的狀態,則其引用計數將始終大於0。先看一段NSTimer使用的例子(ARC模式):xcode
1 #import <Foundation/Foundation.h> 2 @interface Friend : NSObject 3 - (void)cleanTimer; 4 @end
1 #import "Friend.h" 2 @interface Friend () 3 { 4 NSTimer *_timer; 5 } 6 @end 7 8 @implementation Friend 9 - (id)init 10 { 11 if (self = [super init]) { 12 _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) 13 userInfo:nil repeats:YES]; 14 } 15 return self; 16 } 17 18 - (void)handleTimer:(id)sender 19 { 20 NSLog(@"%@ say: Hi!", [self class]); 21 } 22 - (void)cleanTimer 23 { 24 [_timer invalidate]; 25 _timer = nil; 26 } 27 - (void)dealloc 28 { 29 [self cleanTimer]; 30 NSLog(@"[Friend class] is dealloced"); 31 }
在類外部初始化一個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(), ^{ 4 [f release]; 5 });
咱們所期待的結果是,初始化5秒後,f對象被release,f的dealloc方法被調用,在dealloc裏面timer失效,對象被析構。但結果倒是如此:函數
1
2
3
4
5
6
7
8
9
10
|
2015-03-18 18:00:35.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:36.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:37.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:38.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:39.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
//運行了5次後沒按照預想的停下來
2015-03-18 18:00:40.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:41.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:42.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
2015-03-18 18:00:43.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
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的調用者顯式地調用來清理現場。以下:atom
1
2
3
4
5
|
Friend *f = [[Friend alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*
NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
[f cleanTimer];
[f release];
});
|
=======================================spa
(2)blockcode
block在copy時都會對block內部用到的對象進行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環境下對block使用不當都會引發循環引用問題,通常表現爲,某個類將block做爲本身的屬性變量,而後該類在block的方法體裏面又使用了該類自己,簡單說就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的這種循環引用會被編譯器捕捉到並及時提醒。舉例以下,依舊以Friend類爲例子:htm
#import "Friend.h" @interface Friend () @property (nonatomic) NSArray *arr; @end @implementation Friend - (id)init { if (self = [super init]) { self.arr = @[@111, @222, @333]; self.block = ^(NSString *name){ NSLog(@"arr:%@", self.arr); }; } return self; }
咱們看到,在block的實現內部又使用了Friend類的arr屬性,xcode給出了warning, 運行程序以後也證實了Friend對象沒法被析構:對象
網上大部分帖子都表述爲"block裏面引用了self致使循環引用",但事實真的是如此嗎?我表示懷疑,其實這種說法是不嚴謹的,不必定要顯式地出現"self"字眼纔會引發循環引用。咱們改一下代碼,不經過屬性self.arr去訪問arr變量,而是經過實例變量_arr去訪問,以下:
由此咱們知道了,即便在你的block代碼中沒有顯式地出現"self",也會出現循環引用!只要你在block裏用到了self所擁有的東西!但對於這種狀況,目前我不知道該如何排除掉循環引用,由於咱們沒法經過加__weak聲明或者__block聲明去禁止block對self進行強引用或者強制增長引用計數。對於self.arr的狀況,咱們要分兩種環境去解決:
1)ARC環境下:ARC環境下能夠經過使用_weak聲明一個代替self的新變量代替原先的self,咱們能夠命名爲weakSelf。經過這種方式告訴block,不要在block內部對self進行強制strong引用:(若是要兼容ios4.3,則用__unsafe_unretained代替__weak,不過目前基本不需考慮這麼low的版本)
1 self.arr = @[@111, @222, @333]; 2 __weak typeof(self) weakSelf=self; 3 self.block = ^(NSString *name){ 4 NSLog(@"arr:%@", weakSelf.arr); 5 };
2)MRC環境下:解決方式與上述基本一致,只不過將__weak關鍵字換成__block便可,這樣的意思是告訴block:小子,不要在內部對self進行retain了!
=========================================================
(3)委託delegate
在委託問題上出現循環引用問題已是老生常談了,本文也再也不細講,規避該問題的殺手鐗也是簡單到哭,一字訣:聲明delegate時請用assign(MRC)或者weak(ARC),千萬別手賤玩一下retain或者strong,畢竟這基本逃不掉循環引用了!