前言:
在ARC(自動引用技術)前,Objective-c都是手動來分配釋放 釋放 計數內存,其過程非常復雜。
ARC技術推出后,貌似世界和平了很多,但是其實ARC並不等同於Java或者C#中的垃圾回收,ARC計數只是在XCode在編譯的時候自動幫我們加上了釋放 計數+1 計數-1.
內存泄露例子:
然而在一些特殊的情況下,內存泄露依然存在,而且防不慎防,這里講一下Objective-C中Block計數是如何產生內存泄露的,如下代碼
.h中
typedef void (^CompletionBlock)(NSString *aStr); @interface B : NSObject @property (copy) CompletionBlock completionBlock; @property (copy) NSString *str; @end
.m中
@implementation B -(id)init{ self = [super init]; if(self){ self.str = @"init string value"; } return self; } -(void)doAction { __block B *b1 = self; self.completionBlock = ^(NSString *aStr){ b1.str = aStr; }; self.completionBlock(@"new string value"); } -(void)dealloc{ NSLog(@"dealloc B"); } @end
main函數中
B *b = [[B alloc]init]; [b doAction]; b = nil;//這句有和無其實無所謂
上面的程序看似沒有問題,但是實際上對象b永遠無法釋放,原因在於doAction函數,這個函數里面有一個block函數名為completionBlock ,也就是一個函數指針。這個函數指針在調用的時候有使用一個對象,也就是self對象。但是這個block隱形的做了一件事情——將self引用計數+1了,因此這個時候self對象(也就是main函數中的b對象)的引用計數是2,這個時候即使我執行了b=nil,也無法釋放,因為b=nil只是將計數減1了,而真正釋放的唯一條件是引用計數為0。這就是所謂的Block的循環引用。
如何解決:
所以在使用block技術的時候,需要格外小心。有幾個解決方法
approach 1: 讓block里面的self的引用計數不要+1,這個時候做法是將" __block B *b1 = self;"這一行改為," __weak __block B *b1 = self;",表示說“我block里面雖然會用到self,但是別擔心,我不會講引用計數+1的”
approach 2:在doAction函數內存的最后一行添加 self.completionBlock=nil; 因為block內部將self計數+1了,但是如果這個block自己先消亡,那么與之相關的一切都講消亡(當然對於引用計數大於1的對象,不會消亡,只會計數減1)。
附加:
PS:開發中,幾乎每個.m文件都會用到block技術,但是從未發現和在意這個內存泄露問題,這並不是XCode編譯時的優化,而是我們所用到的Block技術(例如AFNetwork GCD Animation)中的block都是匿名Block——即,用完自動釋放。 如果有一天不用匿名block就需要注意這個問題了。
例如下面的例子中,雖然使用了Block,但是沒有泄露,是因為這是一個匿名的Block(即匿名函數指針)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),dispatch_get_main_queue(), ^{ self.view.backgroundColor = [UIColor redColor]; });