内存泄漏是开发中经常会遇到和需要处理的问题,如:循环引用、僵尸对象和野指针、大循环内存峰值。

循环引用

即对象A强引用对象B,对象B强引用对象A,或者多个对象强引用形成一个闭环。block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用。

解决方案:

使用弱引用或主动断开循环。

示例1:block

导致内存泄漏的代码(action block中的self):

1
2
3
4
5
6
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", self.name);
});
};
self.block();

改成这样可以正常dealloc:

1
2
3
4
5
6
7
8
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.name);
});
};
self.block();

或者:

1
2
3
4
5
6
7
8
__block UIViewController *vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil;
});
};
self.block();

或者:

1
2
3
4
5
6
self.block = ^(UIViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
});
};
self.block(self);

示例2:NSTimer

NSTimer会造成循环引用,timer会强引用target即self,一般self又会持有timer作为属性,这样就造成了循环引用。
那么,如果timer只作为局部变量,不把timer作为属性呢?同样释放不了,因为在加入runloop的操作中,timer被强引用。而timer作为局部变量,是无法执行invalidate的,所以在timer被invalidate之前,self也就不会被释放。
所以我们要注意,不仅仅是把timer当作实例变量的时候会造成循环引用,只要申请了timer,加入了runloop,并且target是self,虽然不是循环引用,但是self却没有释放的时机。如下方式申请的定时器,self已经无法释放了。

1
2
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(commentAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

解决这种问题有几个实现方式,大家可以根据具体场景去选择:

  • 增加startTimer和stopTimer方法,在合适的时机去调用,比如可以在viewDidDisappear时stopTimer,或者由这个类的调用者去设置。
  • 每次任务结束时使用dispatch_after方法做延时操作。注意使用weakself,否则也会强引用self。

    1
    2
    3
    4
    5
    6
    7
    - (void)startAnimation
    {
    WS(weakSelf);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [weakSelf commentAnimation];
    });
    }
  • 使用GCD的定时器,同样注意使用weakself。

    1
    2
    3
    4
    5
    6
    7
    WS(weakSelf);
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
    [weakSelf commentAnimation];
    });
    dispatch_resume(timer);

示例3:NSNotification

使用block的方式增加notification,引用了self,在删除notification之前,self不会被释放,与timer的场景类似,其实这段代码已经声明了weakself,但是调用_eventManger方法还是引起了循环引用。
也就是说,即使我们没有调用self方法,_xxx也会造成循环引用。

1
2
3
4
5
6
7
8
[[NSNotificationCenter defaultCenter] addObserverForName:kUserSubscribeNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
if (note) {
Model *model=(Model *)note.object;
if ([model.subId integerValue] == [weakSelf.subId integerValue]) {
[_eventManger playerSubsciption:NO];
}
}
}

僵尸对象和野指针

僵尸对象是指内存已经被回收的对象,指向僵尸对象的指针就是野指针,向野指针发送消息会导致崩溃:

1
EXC_BAD_ACCESS

解决方案:

当对象释放后,应该将其置为nil。

大循环内存峰值

循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏。

解决方案:

在循环中创建自己的autoreleasepool,及时释放占用内存大的临时变量,减少内存占用峰值。

1
2
3
4
5
6
for (int i = 0; i < 5000; i++) {
@autoreleasepool {
NSNumber *num = [NSNumber numberWithInt:i];
[num performOperationOnNumber];
}
}

使用Instruments工具检查内存泄漏

打开Instruments:



内存泄露时:




 评论