Retain Cycle

在iOS4.0推出了Blocks這個語言特性後,到現在iOS都已經出到5.0了,所以我想Blocks應該可以被廣泛應用了
但現在iOS環境是從MRC(Manual Reference Counting) 走到ARC (Automatic Reference Counting)
在Reference Counting的環境中Runtime是無法自動解除Retain cycle的
而Blocks有很多隱性的retain的動作,很容易不小心的造成retain cycle。
而本篇的重點是點出三種會造成Retain Cycle的Anti-patterns,再來講一下怎麼解決。

在討論之前還是先大概重述一些概念,block當中是允許去使用外部的variable,但是local variable是會自動做retain的動作
例如

MyClass* foo = ….;
self.someBlock = ^
{
    [foo bar];
};

上面的foo在此block被copy到heap的時候,也會一起被自動retain,而這就是我說的很容易造成retain cycle的主因。


Anti Pattern 1
第一個例子我們先用大家很常用的Opne source library ASIHttpRequest當作一個範例
看看下面的例子,有發現任何問題嗎?

ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
    NSString *responseString = [request responseString];
    NSData *responseData = [request responseData];
}];

這邊我們要首先要注意的點就是[request setCompletionBlock:…]這裡
很明顯的這邊的用途是要做一個event callback的用途,也就是說我給你一個block,當動作完成的時候callback我。這是一個典型的非同步的作法。
但由於如果你要把block拿來之後使用,你一定要呼叫[aBlock copy]的動作,此動作會把block從stack丟進heap。
因為在iOS的環境block也是一個object,此時這個block的retain count就會增加1
這時候根據定義,這個block中參照的request這個local變數就也會被retain起來。
所以request的retain count也會增加1。
但問題來了,一旦request完成任務,應當要被release的時候
卻會發現retain count始終無法歸零。

理由是 request <-> block 這兩個互相 retain 而無法正常釋放,這就是所謂的retain cycle了。


解決方法很簡單,看看ASIHttpRequest官網的文件也就是用__block來描述request

__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
    NSString *responseString = [request responseString];
    NSData *responseData = [request responseData];
}];

通過block variable不會retain的特性,有點類似weak reference的作用,此時block就不會retain request
當然也就不會有retain cycle的問題。


Anti Pattern 2
Anti Pattern 1是在使用別人的library的時候容易出現的
Anti Pattern 2是在實作自己的class的時候容易出現
請看下面這段code

//MyClass.h
@property <nonatomic, copy=""> MyBlock onCompleteBlock;

//MyClass.c
self.onCompleteBlock = ^{
    [self doSomething];
}

我相信這邊大家已經馬上看出問題在哪裡了,其實Anti Pattern2算是Anti Pattern 1的特例,只是這邊使用的是特殊變數self

但有些時候我們更容易忽略的是在block中始用自己的member variable
例如

//MyClass.h
@interface MyClass : NSObject
{
    NSDate* lastModifed;
}

//MyClass.c
self.onCompleteBlock = ^{
    lastModifed = [[NSDate date] retain];
}

這時候就沒有那麼容易察覺了。根據定義,在使用block的時候,
如果我們使用到member variable,此時retain的不是lastModified指到的object 而是retain self
所以造成的就是 self <-> block 互相 retain
跟anti pattern 1一樣的結果就是無法最終釋放記憶體。

這時候的解決方法也是一樣是拿出__block來用

//MyClass.c
__block MyClass* tempSelf = self;
self.onCompleteBlock = ^{
    tempSelf.lastModifed = [NSDate date];
}


Anti Pattern 3

繼續看下面的code

SettingsViewController* settingsViewController = [[[SettingsViewController alloc] init] autorelease];
settingsViewController.onUpdate = ^
{
    [self doUpdate];
}
self.settingsViewController = settingsViewController;


雖然這個Block中沒有直接使用到settingsViewController,感覺應該不會有retain cycle
但是因為 self. settingsViewController 而 settingsViewController.block 再來 block 里 [self doUpdate]
這就剛好繞了一圈,同樣會有retain cycle。

所以呢,還是要想一樣用anti pattern 2的解法去解決

//RootViewController.m
SettingsViewController* settingsViewController = [[[SettingsViewController alloc] init] autorelease];
__block RootViewController* tempSelf = self;
settingsViewController.onUpdate = ^
{
    [tempSelf doUpdate];
}
self.settingsViewController = settingsViewController;

在reference counting的環境裡,我建議要解決retain cycle的最好思維就是想清楚從屬關係
例如最後一個anti pattern 他們的從屬關係應該就是
RootViewController -> SettingsViewController -> block
如果block要用到SettingsViewController或是RootViewController,則就要使用weak reference (也就是__block)
在這樣的原則之下,就可以知道哪些要給他retain哪些不要了。

最後要補充一點就是上面的例子都是在MRC環境下當做範例,在MRC中__block variable在block中使用是不會retain的
但是ARC中__block則是會Retain的。
取而代之的是用__weak或是__unsafe_unretained來更精確的描述weak reference的目的
其中前者只能在iOS5之後可以使用,但是比較好 (該物件release之後,此pointer會自動設成nil)
而後者是ARC的環境下為了相容4.x的解決方案。
所以上面的範例中

__block MyClass* temp = …;    // MRC環境下使用
__weak MyClass* temp = …;    // ARC但只支援iOS5.0以上的版本
__unsafe_retained MyClass* temp = …;  //ARC且可以相容4.x以前的版本

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值