前言
NSTimer的研究必然涉及到runloop,因为对于runloop研究不够深入,所以此文较为浅显,后期会不断更新。在对NSTimer内存泄漏的探究过程中,学习到OC多继承的技巧,因此此文也会讲到OC多继承。
参考文章
http://blog.ibireme.com/2015/05/18/runloop/ibireme大神runloop文章
http://v.youku.com/v_show/id_XODgxODkzODI0.html sunny大神Runloop视频
http://ios.jobbole.com/87856/ NSProxy多继承demo
NSTimer内存泄漏
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printMessage) userInfo:nil repeats:YES];
@implementation TimerTools
- (void)printMessage {
NSLog(@"%s", __func__);
}
@end
这种方式创建的timer会被 添加到 mainRunloop 的NSDefaultRunLoopMode中,而且会被 mainRunloop 强引用。timer 又会对 Target 进行强引用。只有当timer执行invalidate后,mainRunloop会消除对timer的强引用,timer才会被释放,进而target的内存才会被释放。
所以何时去执行invalidate是一个问题。一般情况下target会是控制器,而我们会选择在dealloc中去执行timer的invalidate。然而控制器的dealloc永远不会执行。
解决办法
单独创建一个对象作为timer的target,此时控制器的dealloc便会执行了。
[NSTimer scheduledTimerWithTimeInterval:1 target:[[TimerTools alloc] init] selector:@selector(printMessage) userInfo:nil repeats:YES];
但是这种解决方案可能会引发一些麻烦,如果timer的action操作需要和控制器做比较复杂的交互,那么你可能会想到block、代理等等,你甚至需要把控制器的某些参数传递给TimerTools对象。我的解决思路是让TimerTools对象只是作为一个第三方,timer告诉target要做什么,然后target通知控制器具体来做什么。方法就是利用runtime的消息转发机制。
@interface TimerTools()
/// weak 引用delegate
@property (nonatomic, weak) id delegate;
@end
@implementation TimerTools
- (instancetype)initWithDelegate:(id)delegate {
self = [super init];
if (self) {
_delegate = delegate;
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return _delegate;
}
在YYKit的微博的demo里面就用到这种思路,YYWeakProxy用于完成类似的工作 ,只是他继承自NSProxy类,对于该类的解析大家可以看上面的链接。只是我觉得没必要继承该类,因为继承该类所需要实现的两个方法:
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
这是runtime消息转发机制中最后的操作,而这种操作相比forwardingTargetForSelector会慢一些而且显得没必要。所谓的慢,主要是因为,转发消息走到这一步会生成NSInvocation对象。
UISCrollView滚动导致的timer停止
timer在运行过程中会遇到一个特殊的情况停止,那就是当UIScrollView滚动的时候,timer会停止。因为timer默认被加到mainRunloop的NSDefaultRunLoopMode当中,而当UIScrollView滚动时mainRunloop会将model切换至UITrackingRunLoopMode,也就是此时mainrunloop只响应UITrackingRunLoopMode。解决方法即将timer添加到UITrackingRunLoopMode当中:
// 很多人是用下面的方法解决问题。但我觉得呢, 因为UITrackingRunLoopMode、NSDefaultRunLoopMode均被标记为common了, 下面的意思是添加到所有被common标记的model当中,所以为什么不是直接加入到UITrackingRunLoopMode ?
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
// 所以我的做法是:
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
OC多继承的实现
通过上面的探究,再加上学习NSProxy讲解的文章,对于多继承的实现也是类似的思路,归根结底是使用协议来实现。
有ATool、BTool、CTool三个类,但是ATool想同时拥有另外两个类的功能,然而OC不允许多继承,现在应用协议来实现多继承。
1、BTool、CTool中分别有- (void)sing;- (void)dance;的功能。
2、创建一个InheritProtocol声明上述的方法,并让ATool采纳该协议。
3、在ATool内部实现消息的转发:
- (id)forwardingTargetForSelector:(SEL)aSelector {
static BTool *bTool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bTool = [[BTool alloc] init];
});
if ([bTool respondsToSelector:aSelector]) {
return bTool;
} else {
return [[CTool alloc] init];
}
}
在外部是能直接这样使用的:
ATool *aTool = [[ATool alloc] init];
[aTool dance];
[aTool sing];
Demo地址https://github.com/Randy1993/NSTimerDemo