循環(huán)引用問題

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, 5
NSEC_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, 5
NSEC_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)引用了信粮!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市趁啸,隨后出現(xiàn)的幾起案子强缘,更是在濱河造成了極大的恐慌,老刑警劉巖不傅,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旅掂,死亡現(xiàn)場離奇詭異,居然都是意外死亡访娶,警方通過查閱死者的電腦和手機(jī)商虐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崖疤,“玉大人秘车,你說我怎么就攤上這事〗俸撸” “怎么了叮趴?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長权烧。 經(jīng)常有香客問我眯亦,道長,這世上最難降的妖魔是什么般码? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任妻率,我火速辦了婚禮,結(jié)果婚禮上板祝,老公的妹妹穿的比我還像新娘宫静。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布囊嘉。 她就那樣靜靜地躺著温技,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扭粱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天震檩,我揣著相機(jī)與錄音琢蛤,去河邊找鬼。 笑死抛虏,一個(gè)胖子當(dāng)著我的面吹牛博其,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迂猴,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼慕淡,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了沸毁?” 一聲冷哼從身側(cè)響起峰髓,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎息尺,沒想到半個(gè)月后携兵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搂誉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年徐紧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炭懊。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡并级,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侮腹,到底是詐尸還是另有隱情嘲碧,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布凯旋,位于F島的核電站呀潭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏至非。R本人自食惡果不足惜钠署,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荒椭。 院中可真熱鬧谐鼎,春花似錦、人聲如沸趣惠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至草戈,卻和暖如春塌鸯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背唐片。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工丙猬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人费韭。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓茧球,卻偏偏與公主長得像,于是被迫代替她去往敵國和親星持。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抢埋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容