簡(jiǎn)介
ARC已經(jīng)出來(lái)很久了,自動(dòng)釋放內(nèi)存的確很方便,但是并非絕對(duì)安全絕對(duì)不會(huì)產(chǎn)生內(nèi)存泄露。導(dǎo)致iOS對(duì)象無(wú)法按預(yù)期釋放的一個(gè)無(wú)形殺手是——循環(huán)引用。循環(huán)引用可以簡(jiǎn)單理解為A引用了B,而B(niǎo)又引用了A,雙方都同時(shí)保持對(duì)方的一個(gè)引用,導(dǎo)致任何時(shí)候引用計(jì)數(shù)都不為0,始終無(wú)法釋放。若當(dāng)前對(duì)象是一個(gè)ViewController,則在dismiss或者pop之后其dealloc無(wú)法被調(diào)用,在頻繁的push或者present之后內(nèi)存暴增,然后APP就掛了。
*注:全文提到的析構(gòu)指的是析構(gòu)函數(shù),oc中特有的函數(shù),析構(gòu)函數(shù)聲明為“-(void)dealloc”這個(gè)函數(shù)我們不能通過(guò)對(duì)象去人為的調(diào)用它,析構(gòu)函數(shù)會(huì)在對(duì)像快要死的時(shí)候自己運(yùn)行,析構(gòu)函數(shù)只能有一個(gè)
下面舉例說(shuō)明在mrc下循環(huán)引用出現(xiàn)的情況:
1.在People.h類(lèi)中聲明一個(gè)Student類(lèi)型的屬性
#import <Foundation/Foundation.h>
@class Student;
@interface People : NSObject
//這里不用retain,如果使用retain的話,會(huì)形成循環(huán)引用
@property(nonatomic,assign,readwrite) Student *stu;
@end
2.在People.m類(lèi)中的dealloc析構(gòu)方法
#import People.h
//#import "Student.h"
@implementation People
- (void)dealloc{
// [_stu release];
[super dealloc];
}
@end
3.在Student.h類(lèi)中聲明一個(gè)People類(lèi)型的屬性
#import <Foundation/Foundation.h>
@class People;
@interface Student : NSObject
@property(nonatomic,retain,readwrite) People *people;
@end
4.在Student.m類(lèi)中的dealloc析構(gòu)方法
#import "Student.h"
#import "People.h"
@implementation Student
- (void)dealloc{
[_people release];
[super dealloc];
}
@end
5.在main.m中測(cè)試其代碼
#import "Student.h"
#import "People.h"
//循環(huán)引用
//是一個(gè)很麻煩的一件事,完全靠經(jīng)驗(yàn)
int main(int argc, const char * argv[]) {
People *p = [[People alloc] init];
Student *stu = [[Student alloc] init];
[p setStudent:stu];//stu計(jì)數(shù):2
[stu setPeople:p];//people計(jì)數(shù):2
[p release]; //people計(jì)數(shù):1
[stu release];//stu計(jì)數(shù):1
//沒(méi)有釋放的原因是dealloc方法中沒(méi)有被執(zhí)行,里面的釋放代碼也就沒(méi)執(zhí)行了,stu和people各自在等待,形成環(huán)狀了
//解決版本就是切斷他們之間的聯(lián)系
//@property中不使用retain,使用assgin
return 0;
}
我們分別定義了一個(gè)People對(duì)象和Student對(duì)象,然后相互引用了,但是當(dāng)我們調(diào)用他們的release方法的時(shí)候,這兩個(gè)對(duì)象并沒(méi)有被釋放
原因很簡(jiǎn)單:
People和Student的相互引用了,當(dāng)執(zhí)行release方法的時(shí)候引用計(jì)數(shù)都還是1,所以就不會(huì)調(diào)用dealloc方法了
dealloc方法中沒(méi)有被執(zhí)行,里面的釋放代碼也就沒(méi)執(zhí)行了,Student和People各自在等待,形成環(huán)狀了
解決的辦法是:
切斷他們之間的聯(lián)系
在一方中定義屬性的時(shí)候,@property中不使用retain,使用assgin
同時(shí)在dealloc方法中不再調(diào)用release方法了
上面的例子中,我們可以看到People類(lèi)中就是使用assgin
下面舉例說(shuō)明在arc下循環(huán)引用出現(xiàn)的情況:
- 定時(shí)器的使用有時(shí)會(huì)導(dǎo)致循環(huán)引用
一方面,NSTimer經(jīng)常會(huì)被作為某個(gè)類(lèi)的成員變量,而NSTimer初始化時(shí)要指定self為target,容易造成循環(huán)引用。 另一方面,若timer一直處于validate的狀態(tài),則其引用計(jì)數(shù)將始終大于0。比如當(dāng)定時(shí)器銷(xiāo)毀的時(shí)機(jī)不對(duì),在dealloc里面銷(xiāo)毀的時(shí)候,內(nèi)存就不會(huì)釋放,就會(huì)造成循環(huán)引用
NSTimer使用的例子
Friend.h文件
1 #import <Foundation/Foundation.h>
2 @interface Friend : NSObject
3 - (void)cleanTimer;
4 @end
Friend.m文件
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:)
13 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 }
在main.m中聲明并且調(diào)用,通過(guò)函數(shù)讓Friend類(lèi)延時(shí)5秒后引用計(jì)數(shù)減一
1 #import "Friend.h"
2
3 //循環(huán)引用
4 //是一個(gè)很麻煩的一件事,完全靠經(jīng)驗(yàn)
5 int main(int argc, const char * argv[]) {
6
7 Friend *friend = [[Friend alloc] init];
8 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 95*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
10 [friend release];
11 });
12
13 return 0;
14 }
我們所期待的結(jié)果是,初始化5秒后,friend對(duì)象被release,friend的dealloc方法被調(diào)用,在dealloc里面timer失效,對(duì)象被析構(gòu)。但結(jié)果卻是如此:

這是為什么呢?主要是因?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)沒(méi)機(jī)會(huì)進(jìn)入dealloc。循環(huán)引用,互相等待,無(wú)窮盡。問(wèn)題的癥結(jié)在于-(void)cleanTimer函數(shù)的調(diào)用時(shí)機(jī)不對(duì),顯然不能想當(dāng)然地放在調(diào)用者的dealloc中。一個(gè)比較好的解決方法是開(kāi)放這個(gè)函數(shù),讓Friend的調(diào)用者顯式地調(diào)用來(lái)清理現(xiàn)場(chǎng)。如下:

- block的使用有時(shí)會(huì)導(dǎo)致循環(huán)引用
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)引用問(wèn)題,一般表現(xiàn)為,某個(gè)類(lèi)將block作為自己的屬性變量,然后該類(lèi)在block的方法體里面又使用了該類(lèi)本身,簡(jiǎn)單說(shuō)就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_(dá)otherVar = ...};block的這種循環(huán)引用會(huì)被編譯器捕捉到并及時(shí)提醒。
block使用的例子
Friend.m文件
1 #import "Friend.h"
2
3 @interface Friend ()
4 @property (nonatomic) NSArray *arr;
5 @end
6
7 @implementation Friend
8 - (id)init
9 {
10 if (self = [super init]) {
11 self.arr = @[@111, @222, @333];
12 self.block = ^(NSString *name){
13 NSLog(@"arr:%@", self.arr);
14 };
15 }
16 return self;
17}
我們看到,在block的實(shí)現(xiàn)內(nèi)部又使用了Friend類(lèi)的arr屬性,xcode給出了warning, 運(yùn)行程序之后也證明了Friend對(duì)象無(wú)法被析構(gòu):

網(wǎng)上大部分帖子都表述為"block里面引用了self導(dǎo)致循環(huán)引用",但事實(shí)真的是如此嗎?我表示懷疑,其實(shí)這種說(shuō)法是不嚴(yán)謹(jǐn)?shù)?,不一定要顯式地出現(xiàn)"self"字眼才會(huì)引起循環(huán)引用。我們改一下代碼,不通過(guò)屬性self.arr去訪問(wèn)arr變量,而是通過(guò)實(shí)例變量_arr去訪問(wèn),如下

由此我們知道了,即使在你的block代碼中沒(méi)有顯式地出現(xiàn)"self",也會(huì)出現(xiàn)循環(huán)引用!只要你在block里用到了self所擁有的東西!但對(duì)于這種情況,目前我不知道該如何排除掉循環(huán)引用,因?yàn)槲覀儫o(wú)法通過(guò)加__weak聲明或者_(dá)_block聲明去禁止block對(duì)self進(jìn)行強(qiáng)引用或者強(qiáng)制增加引用計(jì)數(shù)。對(duì)于self.arr的情況,我們要分兩種環(huán)境去解決
- ARC環(huán)境下:ARC環(huán)境下可以通過(guò)使用_weak聲明一個(gè)代替self的新變量代替原先的self,我們可以命名為weakSelf。通過(guò)這種方式告訴block,不要在block內(nèi)部對(duì)self進(jìn)行強(qiáng)制strong引用:(如果要兼容ios4.3,則用__unsafe_unretained代替__weak,不過(guò)目前基本不需考慮這么low的版本)
1 self.arr = @[@111, @222, @333];
2 __weak typeof(self) weakSelf=self;
3 self.block = ^(NSString *name){
4 NSLog(@"arr:%@", weakSelf.arr);
5 };
- MRC環(huán)境下:解決方式與上述基本一致,只不過(guò)將__weak關(guān)鍵字換成__block即可,這樣的意思是告訴block:小子,不要在內(nèi)部對(duì)self進(jìn)行retain了!
- 代理的使用有時(shí)會(huì)導(dǎo)致循環(huán)引用
聲明delegate時(shí)請(qǐng)用assign(MRC)或者weak(ARC),千萬(wàn)別手賤玩一下retain或者strong,畢竟這基本逃不掉循環(huán)引用了