您的位置:首页 > 移动开发 > IOS开发

<iOS>ARC下需要注意的内存管理

2015-12-23 15:22 501 查看
ARC下需要注意的内存管理

启用ARC后,开发者不需要担心内存管理,编译器会为你处理这一切(注意ARC是编译器特性,而不是iOS运行时特性,更不是其他语言中的垃圾收集器)。

简单来说,编译器在编译代码时,会自动生成实例的引用计数代码,帮助我们完成之前MRC需要完成的工作,不过据说除此之外,编译器也会执行某些优化。

ARC虽然能够解决大部分的内存泄露问题,但是仍然有些地方是我们需要注意的。

循环引用

循环引用简单来说就是两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。比如声明一个delegate时一般用weak而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。

这种简单的循环引用只要在coding的过程中多加注意,一般都可以发现。

解决的办法很简单,一般是将循环链中的一个强引用改为弱引用就可解决。

另一种block引起的循环引用问题。

block引起的循环引用

主要有两条规则:

第一条规则:如果在block中访问了属性,那么block就会retain住self。

第二条规则:如果在block中访问了一个局部变量,那么block就会对该变量有一个强引用,即retain该局部变量。

根据这两条规则,我们可以知道发生循环引用的情况:

//规则1

self.myblock = ^{

//访问成员方法

[self doSomething];

//访问属性

NSLog(@“%@”,weakSelf.str);

}

//规则2

ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];

[request setCompletionBlock:^{

NSString *string = [request responseString];

}];

对象对block拥有一个强引用,而block内部又对外部对象有一个强引用,形成了闭环,发生内存泄露。

怎么解决这种内存泄露呢?

可以用block变量来解决。

官方提供了几种方案,第一种用__block变量:

在MRC中,block id x不会retain住x,但是在ARC中,默认是retain住的x的,我们需要使用_unsafe_unratain __block id x来达到弱引用的效果。

解决方案如下所示:

__unsafe_unretained __block id weakSelf = self;

self.myblock = ^{

[weakSelf doSomething];

NSLog(@“%@”,weakSelf.str);

}

NSTimer的问题

timer用来在未来的某个时刻执行一次或者多次我们指定的方法,那么问题来了,究竟系统是怎么保证timer触发action的时候,我们指定的方法是有效的呢?万一receiver无效了呢?

答案是,系统会自动retain住其接收者,直到其执行我们指定的方法。

+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

可以注意到repeats参数,一次性(repeats为NO)的timer会在触发后自动调用invalidated,而重复性的timer则不会。

现在问题又来了,看看下面这段代码:

- (void)dealloc{

[timer invalidate];

[super dealloc];

}

这个是很容易犯的错,如果这个timer是个重复性的timer,那么self对象就会被timer retain住,这个时候不调用invalidate的话,self对象的引用计数会大于1,dealloc永远不会调用到,这样内存泄露就会发生。

timer都会对它的target进行retain,需要小心对待这个target的生命周期问题,尤其是重复性的timer,同时需要注意在dealloc之前调用invalidate。

performSelector的问题

[self performSelector:@selector(method:) withObject:self.property afterDelay:3];

performSelector延时调用的原理是这样的,执行上面这段函数的时候系统会自动将self.property的retainCount加1,直到selector执行完毕之后才会将self.property的retainCount减1。这样子如果selector一直未执行的话,self就一直不能够被释放掉,就有可能造成内存泄露。

比较好的解决方法是将未执行的perform给取消掉:[NSObject cancelPreviousPerformRequestsWithTarget:self];

因为这种原因产生的泄露因为并不违反任何规则,是Intrument所无法发现的。

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay

官方文档大概意思是系统依靠一个timer来保证延时触发,但是只有在runloop在default mode的时候才会执行成功,否则selector会一直等待run loop切换到default mode。

根据之前关于timer的说法,在这里其实调用performSelector:afterDelay:同样会造成系统对target强引用,也即ratain住。这样子如果selector一直无法执行的话(比如runloop不是运行在defaultMode下),同样会造成target一直无法释放掉,发生内存泄露。

怎样解决这个问题呢?

其实很简单,在适当的时候取消掉该调用就行了,系统提供了接口:

+ (void)cancelPreviousPerformRequestWithTarget:(id)aTarget

关于NSNotification的addObserver与removeObserver问题

会注意到常常会在dealloc里面调用removeObserver,会不会出现上面的问题呢?

答案是否定的,这是因为addObserver只会建立一个弱引用的接收着,所以不会发生内存泄露问题。但是我们需要在dealloc里面调用removeObserver,避免通知的时候,对象已被取消了,这时候会发生crash。

C语言的接口

C语言不能够调用OC中的retain与release,一般C语言接口都提供了Release函数(比如CGContextRelease(context c))来管理内存。ARC不会自动调用这些C接口的函数,所以这还是需要我们自己来进行管理。

下面是一段常见的绘制代码,其中就需要自己调用release接口:

CGContextRef context = CGBitmapContextCreate(NULL, target_w, target_h, 8, 0, rgb, bmi);

CGColorSpaceRelease(rgb);

UIImage *pdfImage = nil;

if (context != NULL) {

        CGContextDrawPDFPage(context, page);

        CGImageRef imageRef = CGBitmapContextCreateImage(context);

        CGContextRelease(context);

        pdfImage = [UIImage imageWithCGImage:imageRef scale:screenScale orientation:UIImageOrientationUp];

        CGImageRelease(imageRef);

}

else

{

       CGContextRelease(context);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: