【原】iOS容易造成循環參考的三種情境,就在你我身邊!

來源:互聯網
上載者:User

標籤:

=======================================================

原創文章,轉載請註明 編程小翁@部落格園,郵件[email protected],Jilon,歡迎各位與我在C/C++/Objective-C/機器視覺等領域展開交流!

 =======================================================

ARC已經出來很久了,自動釋放記憶體的確很方便,但是並非絕對安全絕對不會產生記憶體泄露。導致iOS對象無法按預期釋放的一個無形殺手是——循環參考。循環參考可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數都不為0,始終無法釋放。若當前對象是一個ViewController,則在dismiss或者pop之後其dealloc無法被調用,在頻繁的push或者present之後記憶體暴增,然後APP就duang地掛了。下面列舉我們變成中比較容易碰到的三種循環參考的情形。

(1)計時器NSTimer

一方面,NSTimer經常會被作為某個類的成員變數,而NSTimer初始化時要指定self為target,容易造成循環參考。 另一方面,若timer一直處於validate的狀態,則其引用計數將始終大於0。先看一段NSTimer使用的例子(ARC模式):

1 #import <Foundation/Foundation.h>2 @interface Friend : NSObject3 - (void)cleanTimer;4 @end
 1 #import "Friend.h" 2 @interface Friend () 3 { 4     NSTimer *_timer; 5 } 6 @end 7  8 @implementation Friend 9 - (id)init10 {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)sender19 {20     NSLog(@"%@ say: Hi!", [self class]);21 }22 - (void)cleanTimer23 {24     [_timer invalidate];25     _timer = nil;26 }27 - (void)dealloc28 {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失效,對象被析構。但結果卻是如此:

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!
.......根本停不下來.....

這是為什麼呢?主要是因為從timer的角度,timer認為調用方(Friend對象)被析構時會進入dealloc,在dealloc可以順便將timer的計時停掉並且釋放記憶體;但是從Friend的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。循環參考,互相等待,子子孫孫無窮盡也。問題的癥結在於-(void)cleanTimer函數的調用時機不對,顯然不能想當然地放在調用者的dealloc中。一個比較好的解決方案是開放這個函數,讓Friend的調用者顯式地調用來清理現場。如下:

        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內部用到的對象進行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環境下對block使用不當都會引起循環參考問題,一般表現為,某個類將block作為自己的屬性變數,然後該類在block的方法體裡面又使用了該類本身,簡單說就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的這種循環參考會被編譯器捕捉到並及時提醒。舉例如下,依舊以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的實現內部又使用了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;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,畢竟這基本逃不掉循環參考了!

 

【原】iOS容易造成循環參考的三種情境,就在你我身邊!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.