ARC已經(jīng)出來很久了,自動(dòng)釋放內(nèi)存的確很方便皆的,但是并非絕對(duì)安全絕對(duì)不會(huì)產(chǎn)生內(nèi)存泄露蹄溉。導(dǎo)致iOS對(duì)象無法按預(yù)期釋放的一個(gè)無形殺手是——循環(huán)引用。循環(huán)引用可以簡單理解為A引用了B承桥,而B又引用了A,雙方都同時(shí)保持對(duì)方的一個(gè)引用根悼,導(dǎo)致任何時(shí)候引用計(jì)數(shù)都不為0凶异,始終無法釋放。若當(dāng)前對(duì)象是一個(gè)ViewController挤巡,則在dismiss或者pop之后其dealloc無法被調(diào)用剩彬,在頻繁的push或者present之后內(nèi)存暴增,然后APP就duang地掛了矿卑。下面列舉我們變成中比較容易碰到的三種循環(huán)引用的情形喉恋。
(1)計(jì)時(shí)器NSTimer
一方面,NSTimer經(jīng)常會(huì)被作為某個(gè)類的成員變量母廷,而NSTimer初始化時(shí)要指定self為target轻黑,容易造成循環(huán)引用。 另一方面琴昆,若timer一直處于validate的狀態(tài)氓鄙,則其引用計(jì)數(shù)將始終大于0。先看一段NSTimer使用的例子(ARC模式):
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:)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 }
在類外部初始化一個(gè)Friend對(duì)象业舍,并延遲5秒后將friend釋放(外部運(yùn)行在非arc環(huán)境下)
1 Friend f = [[Friend alloc] init];
2 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5NSEC_PER_SEC), dispatch_get_main_queue(), ^{
4 [f release];
5 });
我們所期待的結(jié)果是抖拦,初始化5秒后,f對(duì)象被release舷暮,f的dealloc方法被調(diào)用蟋座,在dealloc里面timer失效,對(duì)象被析構(gòu)脚牍。但結(jié)果卻是如此:
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!//運(yùn)行了5次后沒按照預(yù)想的停下來
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!
.......根本停不下來.....
這是為什么呢?主要是因?yàn)閺膖imer的角度巢墅,timer認(rèn)為調(diào)用方(Friend對(duì)象)被析構(gòu)時(shí)會(huì)進(jìn)入dealloc诸狭,在dealloc可以順便將timer的計(jì)時(shí)停掉并且釋放內(nèi)存券膀;但是從Friend的角度,他認(rèn)為timer不停止計(jì)時(shí)不析構(gòu)驯遇,那我永遠(yuǎn)沒機(jī)會(huì)進(jìn)入dealloc芹彬。循環(huán)引用,互相等待叉庐,子子孫孫無窮盡也舒帮。問題的癥結(jié)在于-(void)cleanTimer函數(shù)的調(diào)用時(shí)機(jī)不對(duì),顯然不能想當(dāng)然地放在調(diào)用者的dealloc中陡叠。一個(gè)比較好的解決方法是開放這個(gè)函數(shù)玩郊,讓Friend的調(diào)用者顯式地調(diào)用來清理現(xiàn)場。如下:
Friend f = [[Friend alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[f cleanTimer];
[f release];
});
(2)block
block在copy時(shí)都會(huì)對(duì)block內(nèi)部用到的對(duì)象進(jìn)行強(qiáng)引用(ARC)或者retainCount增1(非ARC)枉阵。在ARC與非ARC環(huán)境下對(duì)block使用不當(dāng)都會(huì)引起循環(huán)引用問題译红,一般表現(xiàn)為,某個(gè)類將block作為自己的屬性變量兴溜,然后該類在block的方法體里面又使用了該類本身侦厚,簡單說就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_(dá)otherVar = ...};block的這種循環(huán)引用會(huì)被編譯器捕捉到并及時(shí)提醒。舉例如下拙徽,依舊以Friend類為例子:
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的實(shí)現(xiàn)內(nèi)部又使用了Friend類的arr屬性,xcode給出了warning膘怕, 運(yùn)行程序之后也證明了Friend對(duì)象無法被析構(gòu):
網(wǎng)上大部分帖子都表述為"block里面引用了self導(dǎo)致循環(huán)引用"想诅,但事實(shí)真的是如此嗎?我表示懷疑淳蔼,其實(shí)這種說法是不嚴(yán)謹(jǐn)?shù)牟嗾海灰欢ㄒ@式地出現(xiàn)"self"字眼才會(huì)引起循環(huán)引用。我們改一下代碼鹉梨,不通過屬性self.arr去訪問arr變量讳癌,而是通過實(shí)例變量_arr去訪問,如下:
由此我們知道了存皂,即使在你的block代碼中沒有顯式地出現(xiàn)"self"晌坤,也會(huì)出現(xiàn)循環(huán)引用!只要你在block里用到了self所擁有的東西旦袋!但對(duì)于這種情況骤菠,我們無法通過加__weak聲明或者_(dá)_block聲明去禁止block對(duì)self進(jìn)行強(qiáng)引用或者強(qiáng)制增加引用計(jì)數(shù)。但我們可以通過其他指針來避免循環(huán)引用(多謝xq_120的提醒)疤孕,具體是這么做的:
__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下強(qiáng)引用商乎,避免weakSelf被釋放掉
NSLog(@"%@", strongSelf->_xxView); //不會(huì)導(dǎo)致循環(huán)引用.
};
對(duì)于self.arr的情況,我們要分兩種環(huán)境去解決:
1)ARC環(huán)境下:ARC環(huán)境下可以通過使用_weak聲明一個(gè)代替self的新變量代替原先的self祭阀,我們可以命名為weakSelf鹉戚。通過這種方式告訴block鲜戒,不要在block內(nèi)部對(duì)self進(jìn)行強(qiáng)制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環(huán)境下:解決方式與上述基本一致遏餐,只不過將__weak關(guān)鍵字換成__block即可,這樣的意思是告訴block:小子赢底,不要在內(nèi)部對(duì)self進(jìn)行retain了失都!
(3)委托delegate
在委托問題上出現(xiàn)循環(huán)引用問題已經(jīng)是老生常談了,本文也不再細(xì)講幸冻,規(guī)避該問題的殺手锏也是簡單到哭粹庞,一字訣:聲明delegate時(shí)請(qǐng)用assign(MRC)或者weak(ARC),千萬別手賤玩一下retain或者strong嘁扼,畢竟這基本逃不掉循環(huán)引用了信粮!