1.没有scheduled方式初始化的,需要我们手动调用addTimer:forMode:方法,将timer添加到一个runloop中.
举例:
//默认添加到RunLoop中
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:NO];
//手动添加到RunLoop中
NSTimer *timer2 = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSDefaultRunLoopMode];
2.scheduled方式创建的timer不会立即执行,需要调用fire函数或者手动调用一次目标方法。
3.timer并不是一种实时机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。通常会有50ms-100ms的延迟。NSTimer不是一个实时系统,因此不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。
4.当用户滑动显示屏,则会出现Timer失效,方法不调用的情况
因为一般Timer是运行在RunLoop的default mode上,而ScrollView在用户滑动时,主线程RunLoop会切换到UITrackingRunLoopMode。而这个时候,Timer就不会运行,方法得不到fire。
第一种解决方法:修改RunLoop 运行模式为 NSRunLoopCommonModes
_timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(update:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
第二种解决方法:在子线程中启动timer,添加timer到runloop,注意:子线程中RunLoop需要手动配置开启。
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEntry) object:nil];
thread.name = @"TimerThread";
[thread start];
_timerThread = thread;
}
- (void)threadEntry {
_timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(update:) userInfo:nil repeats:YES];
//子线程RunLoop手动配置开启
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:_timer forMode:NSRunLoopCommonModes];
[runLoop run];
}
- (void)cancel {
//NSTimer要在同一个线程中创建和关闭。因为创建的Timer的时候已经把Timer加入到该线程对应的RunLoop中,这个RunLoop设置了这个Timer为一个事件。因此要在同一个线程中才能cancel这个Timer。
[self performSelector:@selector(invalidTimer) onThread:_timerThread withObject:nil waitUntilDone:YES];
}
- (void)invalidTimer {
if (_timer) {
[_timer invalidate];
_timer = nil;
}
}
5.更为实时的timer,GCDtimer
uint64_t interval = 1 * NSEC_PER_SEC;
//创建一个专门执行timer回调的GCD队列
dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函数设置timer参数
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//设置回调
dispatch_source_set_event_handler(_timer, ^(){
NSLog(@"Timer %@", [NSThread currentThread]);
});
dispatch_resume(_timer);//dispatch_source默认是Suspended状态,通过dispatch_resume函数开始它
其中的dispatch_source_set_timer的最后一个参数,是最后一个参数(leeway),它告诉系统我们需要计时器触发的精准程度。所有的计时器都不会保证100%精准,这个参数用来告诉系统你希望系统保证精准的努力程度。如果你希望一个计时器每5秒触发一次,并且越准越好,那么你传递0为参数。另外,如果是一个周期性任务,比如检查email,那么你会希望每10分钟检查一次,但是不用那么精准。所以你可以传入60,告诉系统60秒的误差是可接受的。它的意义在于降低资源消耗。
6.GCD方式的一次性timer
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"dispatch_after enter timer");
});
7.递归调用实现的timer
- (void)dispatchAfterTimer {
__weak typeof (self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after enter timer,thread = %@", [NSThread currentThread]);
[weakSelf dispatchAfterTimer];
});
}
8.NSTimer都会对它的target强引用,我们需要小心对待这个target的生命周期问题。
比如这样的代码:
self.detectTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:@selector(someMethod)
userInfo:nil
repeats:YES];
- (void)dealloc {
[self.detectTimer invalidate];
self.detectTimer = nil;
}
由于timer对target:self的retain,造成self退出后根本没有调用dealloc,以至于viewController不会被释放导致内存泄露。
那么怎么解决这一问题呢?
方法一:把计时器的释放操作放到viewWillDisappear中来管理。
代码大概长这样:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.detectTimer invalidate];
self.detectTimer = nil;
}
当页面需要频繁地切入切出时这个方法缺点明显。
方法二:使用MSWeaker
MSWeaker实现了一个利用GCD的弱引用的timer。原理是利用一个新的对象,在这个对象中创建了一个队列,然后在这个队列中创建gcd timer.注意其中用到了dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue); 这个是将privateSerialQueue队列的执行操作放到队列dispatchQueue 中去.参考代码:
NSString *privateQueueName = [NSString stringWithFormat:@"com.mindsnacks.msweaktimer.%p", self];
self.privateSerialQueue = dispatch_queue_create([privateQueueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0,
0,
self.privateSerialQueue);
- (void)resetTimerProperties
{
int64_t intervalInNanoseconds = (int64_t)(self.timeInterval * NSEC_PER_SEC);
int64_t toleranceInNanoseconds = (int64_t)(self.tolerance * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer,
dispatch_time(DISPATCH_TIME_NOW, intervalInNanoseconds),
(uint64_t)intervalInNanoseconds,
toleranceInNanoseconds
);
}
- (void)schedule
{
[self resetTimerProperties];
__weak MSWeakTimer *weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
[weakSelf timerFired];
});
dispatch_resume(self.timer);
}
方法三:考虑引入一个对象,在这个对象中弱引用self,然后将这个对象传递给timer的构建方法 这里可以参考YYWeakProxy建立这个对象.
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
//当不能识别方法时候,就会调用这个方法,在这个方法中,我们可以将不能识别的传递给其它对象处理
//由于这里对所有的不能处理的都传递给_target了,所以methodSignatureForSelector和forwardInvocation不可能被执行的,所以不用再重载了吧
//其实还是需要重载methodSignatureForSelector和forwardInvocation的,为什么呢?因为_target是弱引用的,所以当_target可能释放了,当它被释放了的情况下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation没实现的话,就直接crash了!!!
//这也是为什么这两个方法中随便写的!!!
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
使用方式:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[YYWeakProxy proxyWithTarget:self ] selector:@selector(animationTimerDidFired:) userInfo:nil repeats:YES];
方法四:block方式来解决循环引用
@interface NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(xx_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)xx_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
block();
}
}
@end
注意以上NSTimer的target是NSTimer类对象,类对象本身是个单例,此处虽然也是循环引用,但是由于类对象不需要回收,所以没有问题.但是这种方式要注意block的间接循环引用,当然了,解决block的间接循环引用很简单,定义一个weak变量,在block中使用weak变量即可
参考: