OC_定时器1 里,
我对定时器做了个简单的介绍,下面说一下NSTimer跟Runloop的联系。
首先,我先把设置定时器的所有方法列举一遍
看一下NSTimer类的头文件:
/* NSTimer.h
Copyright (c) 1994-2016, Apple Inc. All rights reserved.
*/
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface NSTimer : NSObject
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;// 创建一个定时器,但是并没有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;// 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
/// Creates and returns a new NSTimer object initialized with the specified block object.
/// - parameter: timeInterval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
// 创建一个定时器,但是并没有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the current run loop in the default mode.
/// - parameter: ti The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
// 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/// Initializes a new NSTimer object using the block as the main body of execution for the timer.
/// - parameter: fireDate The time at which the timer should first fire.
/// - parameter: interval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
// 默认的初始化方法,(创建定时器后,手动添加到 运行循环,并且手动触发才会启动定时器)
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
- (void)fire;
@property (copy) NSDate *fireDate;
@property (readonly) NSTimeInterval timeInterval;
// Setting a tolerance for a timer allows it to fire later than the scheduled fire date, improving the ability of the system to optimize for increased power savings and responsiveness. The timer may fire at any time between its scheduled fire date and the scheduled fire date plus the tolerance. The timer will not fire before the scheduled fire date. For repeating timers, the next fire date is calculated from the original fire date regardless of tolerance applied at individual fire times, to avoid drift. The default value is zero, which means no additional tolerance is applied. The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of this property.
// As the user of the timer, you will have the best idea of what an appropriate tolerance for a timer may be. A general rule of thumb, though, is to set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance will have a significant positive impact on the power usage of your application. The system may put a maximum value of the tolerance.
@property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0);
- (void)invalidate;
@property (readonly, getter=isValid) BOOL valid;
@property (nullable, readonly, retain) id userInfo;
@end
NS_ASSUME_NONNULL_END
参数repeats是指定是否循环执行,YES将循环,NO将只执行一次。
从该头文件中可以看到,设置定时器的方法有8种,但是仔细看其实很容易看出真正的实现方式就三种,只是有几种不同的书写方式,有些是使用block:
1、timerWithTimeInterval 这种类方法创建出来的对象如果不用 addTimer: forMode方法手动加入主循环池中,将不会循环执行
2、scheduledTimerWithTimeInterval 这种方法会将定时器添加到当前的运行循环,运行循环的模式为默认模式。
3、instancetype方法需要手动加入循环池,它会在设定的启动时间启动。
下面用代码分析区别:
//
// ViewController.m
// KSenTimerDemo
//
// Created by ehsy-pc on 2017/10/31.
// Copyright © 2017年 ehsy-pc. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Do any additional setup after loading the view, typically from a nib.
// [self scheduledTimerWithTimeInterval];//主线程里设置定时器
[self scheduledTimerWithTimeInterval2];//在子线程里设置定时器
// [self timerWithTimeInterval];
}
/**
scheduledTimerWithTimeInterval 方式设置定时器
*/
-(void)scheduledTimerWithTimeInterval
{
NSTimer *timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES];
}
/**
scheduledTimerWithTimeInterval 方式 在子线程里设置定时器,
*/
-(void)scheduledTimerWithTimeInterval2
{
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSTimer *timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES];
// [[NSRunLoop currentRunLoop] run];
});
}
/**
scheduledTimerWithTimeInterval 方式设置定时器
*/
-(void)timerWithTimeInterval
{
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void)myLog:(NSTimer *)timer
{
NSLog(@"--111111--");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
- 当我们调用方法 [self timerWithTimeInterval] 时,发现定时器并没有进行打印操作,而把这句 //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]注释去掉,重新运行, 计时器开始执行打印操作,说明timerWithTimeInterval这种方式创建的定时器并没有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
- 当我们运行方法[self scheduledTimerWithTimeInterval] 时,发现定时器开始执行打印操作,说明 scheduledTimerWithTimeInterval 这种方式创建timer的时候 把它指定到了一个默认的runloop模式中(runloop的运行模式kCFRunLoopDefaultMode),并且在 TimeInterval时间后 启动了定时器。
- 当我们运行方法[self scheduledTimerWithTimeInterval2] 时,发现定时器开始没有执行打印操作,虽然scheduledTimerWithTimeInterval 这种方式创建timer的时候 已经把它指定到了一个默认的runloop模式中,但是放在子线程时,runloop并没有启动,需要 用这句代码 [[NSRunLoop currentRunLoop] run] 进行启动,然后重新运行 发现可以正常进行打印操作。注意:只需要加入 [[NSRunLoop currentRunLoop] run] 就可以,并不需要 加入‘[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]’,因为 scheduledTimerWithTimeInterval 这种方式创建timer的时候 已经把它指定到了一个默认的runloop模式中,不需要再次添加,只需要我们去启动。
NSTimer为什么要添加到RunLoop中才会有作用
NSTimer其实也是一种资源,如果看过多线程变成指引文档的话,我们会发现所有的源如果要起作用,就得加到runloop中去。同理,计时器这种资源要想起作用,那肯定也需要加到runloop中才会有效。
如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。
你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下主线程的runloop,你会发现有很多资源(NSLog(@"runloop:%@",[NSRunLoop mainRunLoop]);)。
1.每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。
2.模式是否正确
我们前面自己动手添加runloop的时候,可以看到有一个参数runloopMode,这个参数是干嘛的呢?
前面提到了要求定时器生效,我们就得把它添加到指定runloop的指定模式中去,通常是主线程的defalut模式。但有时我们这样做了,仍然发现timer还没有有触发事件。这是为什么呢?
这是因为定时器添加的时候,我们需要指定一个模式中,因为同一线程的runloop在运行的时候,任意时刻只能处于一种模式。所以只能当程序处于这种模式的时候,定时器才能得到触发事件的机会。
综上,要让计时器生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。
github:https://github.com/kinsen17/NSTimerWithRunloopDemo