引言
我们都知道timer在使用的时候有很多坑,比如强引用target导致循环引用,甚至内存泄露问的,timer触发时机不准的问题,子线程中的timer要手动加入runloop...
我在timer中的那些坑里已经说过这些问题了,在最后我们提到了自释放这个概念,今天就来聊聊我是如何实现自释放timer的。
两个重要问题
我们知道timer的循环引用主要原因有两个:
1.timer基于runloop。
2.timer强引用自己的target
理解了循环引用的原因,就迈出了实现自释放timer的第一步。
我们现在分析自释放timer要想达到效果关键点在哪里?
1.如何打破timer的循环引用?
2.如何做到自释放?
这两个问题是有先后关系的,只有解决了问题一,才能解决问题二,下面我们就来看看如何解决这两个问题。
如何打破timer的循环引用?
解决timer循环引用的问题,我们就要看产生循环引用的原因:
1.timer基于runloop,这是一个事实,是无法改变的,这里可以把runloop简单的看成是系统,就是说timer是基于系统的,如果timer的回调还没有发生,那么系统是不会释放这个timer的,不管这个timer是不是全局变量或局部变量,或者压根就没声明。深刻理解了这一点,我们就会知道,这个是系统合理的做法,我们很难在这一点上有什么突破。
2.timer强引用自己的target,这也是一个事实,但是我们会想,可不是以不把这个target设置成使用timer的那个对象呢?比如一个vc要用timer,timer的target并不指向这个vc,这样,这个vc要销毁的时候就不受timer牵制了,即使在dealloc里手动调用invalidate也不会造成循环引用的尴尬了。
这是我想到的一个思路,那么问题又来了:我们把这个target指向谁呢?答案:NSTimer的类对象。类对象也是一个对象,不理解的可以看看我的另一篇文章为什么object_getClass(obj)与[OBJ class]返回的指针不同在这个文章总有说明,其实类对象就像是一个单例,每个类在程序运行的时候都有一个这样的单例的类的对象存在于段内存中。
我们利用这一点可以把引用循环打破,但是问题又来了:如果不指向vc这个self,回调如何给到vc呢?
这里的解决办法是,自定义一个timer的初始化方法,vc可以传入一个block作为回调,这个block作为了这个timer对象的userInfo保存起来,因为我们把这个timer对象的target设置成了自身的类对象,这样其实timer的回调是回调给了NSTimer,而回调方法传递的参数正式这个timer对象本身,从这个timer对象中取出userInfo字段,其实就是vc设置的block,此时执行这个block就把回调转发给了vc。
到此就可以解决timer循环引用的问题了。
如何做到自释放?
完成了第一步,其实就可以在dealloc中调用invalidate方法了,完成了第一步其实timer就变得容易使用了,但是要想做到极致,就是dealloc中也不想写一句代码就搞定这一切,就要想第二个问题:如何做到自释放?
这个问题相对简单一点,就是:其实应该在dealloc方法中去手动释放,如何能做到在dealloc方法执行的时候不用写代码就能将timer释放?有些人可能已经猜到了,答案就是AOP。我们这里交换的是dealloc的方法实现,但是如果我们直接用@selector(dealloc)的方式去交换dealloc的实现的时候编译器是报错的,大概意思就是不允许我们交换dealloc方法,我们这里可以用NSSelectorFromString(@"dealloc")的方式巧妙的避过这个问题。
当我们交换了dealloc方法,实际上就知道了使用timer的对象的dealloc时机,在这里面我们用runtime拿到该vc的ivarlist,判断如果该ivar是timer,且该timer.isValid = YES,此时我们调用invalidate就可以了。
到此自释放timer的思路就讲完了,下面上代码。
##GJTimer
GJTimer.h
/*!
@header GJTimer.h
@abstract NSTimer's category
@discussion NSTimer会强引用target而导致有可能出现循环引用的问题,该Category主要解决循环引用
并简单的实现自释放功能,一个对象的timer属性或变量并不需要考虑在合适的时机调用invalidate
timer会在该对象销毁的时候自动invalidate。
@author guoxiaoliang850417@163.com
*/
#import <Foundation/Foundation.h>
typedef void (^GJSimpleBlock)();
@interface NSTimer(GJWeakTimer)
/**
* 创建timer对象
* @param ti timeInterval
* @param yesOrNo repeat
* @param block timer处理具体事件的回调
* @param aTarget 持有timer作为属性或字段的类
* @return timer对象
*/
+ (NSTimer *)gjw_scheduledWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo block:(GJSimpleBlock)block target:(id)aTarget;
/**
* 暂停
*/
- (void)gjw_pauseTimer;
/**
* 复位
*/
- (void)gjw_resumeTimer;
/**
* delay interval 之后复位
* @param interval timeInterval
*/
- (void)gjw_resumeTimerAfterTimeInterval:(NSTimeInterval)interval;
@end
GJTimer.m
#import "GJTimer.h"
#import <objc/runtime.h>
@interface NSObject(GJTimerTarget)
@end
@implementation NSObject(GJTimerTarget)
- (void)gjw_dealloc {
u_int count;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *ivarName = [[NSString alloc] initWithCString:ivar_getTypeEncoding(ivars[i]) encoding:NSUTF8StringEncoding];
if ([ivarName rangeOfString:@"NSTimer"].location != NSNotFound) {
NSTimer *tempTimer = (NSTimer *)object_getIvar(self, ivars[i]);
if (tempTimer.isValid) {
[tempTimer invalidate];
}
}
}
[self gjw_dealloc];
}
@end
@implementation NSTimer(GJWeakTimer)
#pragma mark - public methods
+ (NSTimer *)gjw_scheduledWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo block:(GJSimpleBlock)block target:(id)aTarget {
NSTimer *tempTimer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(p_blockInvoke:) userInfo:[block copy] repeats:yesOrNo];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(class_getInstanceMethod([aTarget class], NSSelectorFromString(@"dealloc")), class_getInstanceMethod([aTarget class], @selector(gjw_dealloc)));
});
return tempTimer;
}
- (void)gjw_pauseTimer {
if (self.isValid) {
[self setFireDate:[NSDate distantFuture]];
}
}
- (void)gjw_resumeTimer {
[self gjw_resumeTimerAfterTimeInterval:0];
}
- (void)gjw_resumeTimerAfterTimeInterval:(NSTimeInterval)interval {
if (![self isValid]) {
[self setFireDate:[NSDate dateWithTimeIntervalSinceNow:interval]];
}
}
#pragma mark - private methods
+ (void)p_blockInvoke:(NSTimer *)sender {
GJSimpleBlock block = sender.userInfo;
if (block) {
block();
}
}
@end
##****结论
以上是我实现自释放timer的思路,希望对大家有帮助,源码方法了github上的一个GJGroup组里,这是我和同事们一起成立的一个组,希望大家支持这个组里的其他项目,多谢。