前言:
在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];
});
