<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);
}
启用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);
}
相关文章推荐
- iOS自适应label高度和宽度
- iOS中GCD的使用小结
- IOS 上拉加载,下拉刷新,本人使用MJRefresh
- iOS判断字符串是否包含空格
- iOS中的事件传递和响应者链条
- iOS应用程序生命周期(前后台切换,应用的各种状态)详解
- IOS 解决真机调试找不到设备问题
- Habber - IOS XMPP 客户端 教程 (三)着手制作好友列表
- 3D触控简介:建立数字刻度应用及快速活动栏
- iOS中nil和release 的作用区别
- 前言,学习ios编程(坚持)
- IOS AsyncSocket封装、长连接
- IOS AFN请求封装使用
- ios NSURLSession(iOS7后,取代NSURLConnection)使用说明及后台工作流程分析
- IOS 获取链接的Wifi的IP、名字,检测是否连接到WLAN
- iOS 即时视频和聊天(基于环信)
- iOS 设置屏幕方向的两种方法
- IOS 解决tableview分割线右缩进15像素问题
- iOS开发实用技术之音频开发
- 151223iOS应用崩溃日志分析 iOS应用崩溃日记揭秘