iOS容易造成循環(huán)引用的三種場景

  • 整理自 編程小翁@博客園

  • http://www.cnblogs.com/wengzilin/p/4347974.html

  • ARC已經(jīng)出來很久了赠幕,自動釋放內(nèi)存的確很方便,但是并非絕對安全絕對不會產(chǎn)生內(nèi)存泄露妆够。導(dǎo)致iOS對象無法按預(yù)期釋放的一個無形殺手是——循環(huán)引用。循環(huán)引用可以簡單理解為A引用了B,而B又引用了A皮官,雙方都同時保持對方的一個引用叭爱,導(dǎo)致任何時候引用計數(shù)都不為0撮躁,始終無法釋放。若當(dāng)前對象是一個ViewController买雾,則在dismiss或者pop之后其dealloc無法被調(diào)用把曼,在頻繁的push或者present之后內(nèi)存暴增,然后APP就duang地掛了漓穿。下面列舉我們變成中比較容易碰到的三種循環(huán)引用的情形嗤军。

(1)計時器NSTimer

  • 一方面,NSTimer經(jīng)常會被作為某個類的成員變量晃危,而NSTimer初始化時要指定self為target叙赚,容易造成循環(huán)引用。 另一方面僚饭,若timer一直處于validate的狀態(tài)震叮,則其引用計數(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 }
  • 在類外部初始化一個Friend對象鳍鸵,并延遲5秒后將friend釋放(外部運行在非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(), ^{
3             [f release];
4         });
  • 我們所期待的結(jié)果是苇瓣,初始化5秒后,f對象被release偿乖,f的dealloc方法被調(diào)用击罪,在dealloc里面timer失效哲嘲,對象被析構(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!//運行了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!//.......根本停不下來.....
  • 這是為什么呢媳禁?主要是因為從timer的角度撤蚊,timer認(rèn)為調(diào)用方(Friend對象)被析構(gòu)時會進(jìn)入dealloc,在dealloc可以順便將timer的計時停掉并且釋放內(nèi)存损话;但是從Friend的角度侦啸,他認(rèn)為timer不停止計時不析構(gòu),那我永遠(yuǎn)沒機會進(jìn)入dealloc丧枪。循環(huán)引用光涂,互相等待,子子孫孫無窮盡也拧烦。問題的癥結(jié)在于-(void)cleanTimer函數(shù)的調(diào)用時機不對忘闻,顯然不能想當(dāng)然地放在調(diào)用者的dealloc中。一個比較好的解決方法是開放這個函數(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時都會對block內(nèi)部用到的對象進(jìn)行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環(huán)境下對block使用不當(dāng)都會引起循環(huán)引用問題债沮,一般表現(xiàn)為炼吴,某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身疫衩,簡單說就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_(dá)otherVar = ...};block的這種循環(huán)引用會被編譯器捕捉到并及時提醒硅蹦。舉例如下,依舊以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的實現(xiàn)內(nèi)部又使用了Friend類的arr屬性童芹,xcode給出了warning, 運行程序之后也證明了Friend對象無法被析構(gòu):
01.png
  • 網(wǎng)上大部分帖子都表述為block里面引用了self導(dǎo)致循環(huán)引用鲤拿,但事實真的是如此嗎假褪?我表示懷疑,其實這種說法是不嚴(yán)謹(jǐn)?shù)慕辏灰欢ㄒ@式地出現(xiàn)"self"字眼才會引起循環(huán)引用生音。我們改一下代碼,不通過屬性self.arr去訪問arr變量幕庐,而是通過實例變量_arr去訪問久锥,如下:
02.png
  • 由此我們知道了,即使在你的block代碼中沒有顯式地出現(xiàn)"self"异剥,也會出現(xiàn)循環(huán)引用瑟由!只要你在block里用到了self所擁有的東西!但對于這種情況,我們無法通過加\__weak聲明或者_(dá)_block聲明去禁止block對self進(jìn)行強引用或者強制增加引用計數(shù)歹苦。但我們可以通過其他指針來避免循環(huán)引用(多謝xq_120的提醒)青伤,具體是這么做的:
__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下強引用,避免weakSelf被釋放掉
NSLog(@"%@", strongSelf->_xxView); //不會導(dǎo)致循環(huán)引用.
};
  • 對于self.arr的情況殴瘦,我們要分兩種環(huán)境去解決:

    • 1)ARC環(huán)境下:ARC環(huán)境下可以通過使用_weak聲明一個代替self的新變量代替原先的self狠角,我們可以命名為weakSelf。通過這種方式告訴block蚪腋,不要在block內(nèi)部對self進(jìn)行強制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)部對self進(jìn)行retain了悠砚!

(3)委托delegate

  • 在委托問題上出現(xiàn)循環(huán)引用問題已經(jīng)是老生常談了晓勇,本文也不再細(xì)講,規(guī)避該問題的殺手锏也是簡單到哭灌旧,一字訣:聲明delegate時請用assign(MRC)或者weak(ARC)绑咱,千萬別手賤玩一下retain或者strong,畢竟這基本逃不掉循環(huán)引用了枢泰!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末描融,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宗苍,更是在濱河造成了極大的恐慌稼稿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讳窟,死亡現(xiàn)場離奇詭異,居然都是意外死亡敞恋,警方通過查閱死者的電腦和手機丽啡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硬猫,“玉大人补箍,你說我怎么就攤上這事⌒ッ郏” “怎么了坑雅?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衬横。 經(jīng)常有香客問我裹粤,道長,這世上最難降的妖魔是什么蜂林? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任遥诉,我火速辦了婚禮拇泣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矮锈。我一直安慰自己霉翔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布苞笨。 她就那樣靜靜地躺著债朵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瀑凝。 梳的紋絲不亂的頭發(fā)上葱弟,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音猜丹,去河邊找鬼芝加。 笑死,一個胖子當(dāng)著我的面吹牛射窒,可吹牛的內(nèi)容都是我干的藏杖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼脉顿,長吁一口氣:“原來是場噩夢啊……” “哼蝌麸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起艾疟,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤来吩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蔽莱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弟疆,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年盗冷,在試婚紗的時候發(fā)現(xiàn)自己被綠了怠苔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡仪糖,死狀恐怖柑司,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锅劝,我是刑警寧澤攒驰,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站故爵,受9級特大地震影響玻粪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一奶段、第九天 我趴在偏房一處隱蔽的房頂上張望饥瓷。 院中可真熱鬧,春花似錦痹籍、人聲如沸呢铆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棺克。三九已至,卻和暖如春线定,著一層夾襖步出監(jiān)牢的瞬間娜谊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工斤讥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纱皆,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓芭商,卻偏偏與公主長得像派草,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铛楣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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

  • http://www.cnblogs.com/wengzilin/p/4347974.html
    陳阿琦閱讀 294評論 0 0
  • ARC已經(jīng)出來很久了近迁,自動釋放內(nèi)存的確很方便,但是并非絕對安全絕對不會產(chǎn)生內(nèi)存泄露簸州。導(dǎo)致iOS對象無法按預(yù)期釋放的...
    DVWang閱讀 499評論 0 0
  • 簡介 ARC已經(jīng)出來很久了鉴竭,自動釋放內(nèi)存的確很方便,但是并非絕對安全絕對不會產(chǎn)生內(nèi)存泄露岸浑。導(dǎo)致iOS對象無法按預(yù)期...
    過眼浮雲(yún)閱讀 728評論 0 4
  • ARC已經(jīng)出來很久了搏存,自動釋放內(nèi)存的確很方便,但是并非絕對安全絕對不會產(chǎn)生內(nèi)存泄露助琐。導(dǎo)致iOS對象無法按預(yù)期釋放的...
    BigLuckyHaha閱讀 481評論 0 6
  • 戰(zhàn)狼2已經(jīng)上映很久了祭埂,一直不情愿買票,不知道是什么情緒作怪兵钮,但是,終究敵不過媒體的連環(huán)轟炸舌界,每天都看到關(guān)于此電影的...
    Blair905閱讀 227評論 0 0