ARC下需要注意的内存管理

ARC下需要注意的内存管理

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

循环引用

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

这种简单的循环引用只要在coding的过程中多加注意,一般都可以发现。
解决的办法也很简单,一般是将循环链中的一个强引用改为弱引用就可解决。
另外一种block引起的循环引用问题,通常是一些对
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变量:

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

那么解决方案就如下所示:

__block id weakSelf = self;  //MRC
//__unsafe_unretained __block id weakSelf = self;   ARC下面用这个
self.myblock = ^{
    [weakSelf doSomething];  
    NSLog(@"%@", weakSelf.str);  
};

performSelector的问题

[self performSelector:@selector(foo:) withObject:self.property afterDelay:3]; performSelector延时调用的原理是这样的,执行上面这段函数的时候系统会自动将self.property的retainCount加1,直到selector执行完毕之后才会将self.property的retainCount减1。这样子如果selector一直未执行的话,self就一直不能够被释放掉,就有可能照成内存泄露。比较好的解决方案是将未执行的perform给取消掉: [NSObject cancelPreviousPerformRequestsWithTarget:self]; 因这种原因产生的泄露因为并不违反任何规则,是Intrument所无法发现的。

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对象就会被timerretain住,这个时候不调用invalidate的话,self对象的引用计数会大于1,dealloc永远不会调用到,这样内存泄露就会发生。
timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer,同时需要注意在dealloc之前调用invalidate。

关于performSelector:afterDelay的问题

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

大概意思是系统依靠一个timer来保证延时触发,但是只有在runloop在default mode的时候才会执行成功,否则selector会一直等待run loop切换到default mode。
根据我们之前关于timer的说法,在这里其实调用performSelector:afterDelay:同样会造成系统对target强引用,也即retain住。这样子,如果selector一直无法执行的话(比如runloop不是运行在default model下),这样子同样会造成target一直无法被释放掉,发生内存泄露。

怎么解决这个问题呢?
其实很简单,我们在适当的时候取消掉该调用就行了,系统提供了接口:

+(void)cancelPreviousPerformRequestsWithTarget:(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,尽量不要使用MRC。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值