在arc循环引用的案例中,其中有一种是timer导致的。
主要原因归结为
举例来说明
//TimerController.h
#import@interface TimerController : UIViewController
@property(nonatomic,strong) NSTimer* timer;
@end
//TimerController.m
-(void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSTimer* tmpTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(p_doSomeThing)
userInfo:nil
repeats:YES];
}
如果repeats = NO 是不会产生循环引用问题的。 因为自动释放池会释放tmpTimer中所分配的内存,这样保留环也就不存在了。
那么如果repeats = YES, 那么就会产生tmpTimer保留self,并且一直会被保留,导致控制器的dealloc方法没法被调用,导致内存泄漏。并且如果不执行invalidate定时器仍然继续执行。所以我觉得这里叫循环引用不是很贴切,因为tmpTimer所指向的那段内存没法被释放。
那么tmpTimer内存何时能被释放呢? timer调用invalidate的时候,为了能在适当的地方可以调用invalidate,那么我们用一个属性保存tmpTimer,即self.timer = tmpTimer;
这样我们明显看出几个问题,timer强引用了tmpTimer这块内存,tmpTimer又强引用了self,self又强引用了timer, 明显的循环引用产生了。 那么是不是将timer设成weak就解决问题了呢? 显然不是的,原因如我上面所说,timer引起内存泄漏可以概括为两个原因
1. timer对target的强引用
2.由于是repeats,timer所指向的那块内存是没办法让releasepool释放的。
所以仅仅用循环引用来描述是不恰当的。
invalidate的功效一方面是停止定时器的执行,另一方面让自动释放池知道tmpTimer那段内存可以进行回收了。那么解决问题的一种思路 只要在适当的地方调用invalidate即万事大吉了。控制器dealloc方法中肯定是不合适了,因为这个方法都没机会给系统吊起。
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[_timer invalidate];
_timer = nil;
}
这个方法中调用是可行的,并且通过调试发现内存泄漏确实没有了。如果controller出栈时候这么处理也蛮合理的,那么如果是由该控制器跳转到另一控制器的情况呢,在这个地方调用就不合适了。
所以合理的逻辑应该是要在该对象销毁时才调用invalidate。
以上是我对timer引起内存泄漏的一些自己的分析,那么解决方案如何? 网上有很多,这里拿来其中一种方案
在控制器对象中使用
_timer = [TimerWeakTarget scheduledTimerWithTimeInterval:1 target:self selector:@selector(p_doSomeThing) userInfo:nil repeats:YES];即可