说明:
这个类对于大家并不陌生, 每当提到NSTimer时, 我想大家的第一反应就是:”不就是个计时器吗!!!, 切~~~”, 好的, 这样反应就对了, 那就说明我的这篇博客对您还是有点作用的.请你耐心的看下去, 我想会对你有点启发的.
NSTimer其实是将一个监听加入到系统的RunLoop中去,当系统runloop到了执行timer条件的循环时,会调用timer一次,当timer执行完,也就是回调函数执行之后,timer会再一次的将自己加入到runloop中去继续监听, 如此循环调用. 那么为什么要把timer加到RunLoop中才能执行呢? 我查找了一些资料, 资料上说: NSTimer其实是一种资源(source), 所有的source要起作用的话, 就得加入到RunLoop中, 同理, NSTimer要起作用也得将它添加到RunLoop中.
文章中尽量不使用或少使用封装, 目的是让大家清楚为了实现功能所需要的官方核心API是哪些(如果使用封装, 会在封装外面加以注释)
- 此文章由 @黑子 编写. 经 @Scott,@春雨 审核. 若转载此文章,请注明出处和作者
NSTimer的创建方法及它的属性
核心API
class: NSTimer
delegate: 无
涉及的API:(API的官方详细注释(英文)详见本章结尾)
/** NSTimer的五种创建方法, 两种scheduled方法, 两种timerWith方法, 一种initWith方法 */
1 + (NSTimer *)scheduledTimerWithTimeInterval (NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats
2 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
3 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats
4 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
5 - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
功能实现
思路
首先我先对NSTimer的五种创建方法进行实现
再对属性进行解释和代码分析.
(1). 两种timerWith方法的实现
/** 先创建NSInvocation类的对象, 该类可以直接调用指定对象的方法, 和performSelector:withObject: 方法类似. NSInvocation也是一种消息调用的方法,并且它的参数没有限制, 但是用 performSelector:withObject: 方法只能传一个参数 */
//创建一个函数签名,这个签名可以是任意的,但需要注意,签名函数的参数数量要和调用的一致。
NSMethodSignature * signature = [NSNumber instanceMethodSignatureForSelector:@selector(init)];
NSInvocation *invo = [NSInvocation invocationWithMethodSignature:signature];
/** 指定执行方法的对象 */
invo.target = self;
/** 指定被调用的方法 */
invo.selector = @selector(timerAction);
/** 创建对象 */
NSTimer timer = [NSTimer timerWithTimeInterval:0.5 invocation:invo repeats:YES];
/** 手动将timer添加到循环池中 */
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
/** 手动启动计时器 */
[timer fire];
/** 创建对象 */
NSTimer timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
/** 手动将timer添加到循环池中 */
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
/** 手动启动计时器 */
[timer fire];
(2). 两种scheduled方法的实现
/** 创建对象, 该方法是我们常用的创建的方法 */
NSTimer timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
/** 先创建NSInvocation类的对象 */
NSInvocation *invo = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(init)]];
/** 指定执行方法的对象 */
[invo setTarget:self];
/** 指定被调用的方法 */
[invo setSelector:@selector(timerAction)];
/** 创建对象 */
NSTimer timer = [NSTimer scheduledTimerWithTimeInterval:1 invocation:invo repeats:YES];
(3). initWith方法的实现
NSTimer timer = [[NSTimer alloc] initWithFireDate:[NSDate distantPast] interval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
/** 手动将timer添加到循环池中, 不需要手动启动计时器 */
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
对于这五种创建方法的总结:
参数repeats: repeats的类型是BOOL, 用来指定是否循环执行, 若设置为NO执行一次, 若设置为YES程序将会循环执行
参数userInfo: 用来设置用户的信息, 可以为空
对于用timerWith方法创建的对象, 必须手动用addTimer: forMode方法将NSTimer对象手动添加到循环池中, 并且手动启动计时器, 否则计时器将不会启动.
对于用scheduled方法创建的对象, 不需要手动添加和启动, 系统会帮我们完成的,
对于用initWith方法创建的对象, 需要手动加入循环池,但不需要手动启动计时器.
注意: 为NSTimer的对象指定target时,指定的对象的引用计数会加1(既上面代码中的self的引用计数会加1, 原理和向数组添加对象或向视图上添加子视图是一个道理), 这样就会造成self不会给释放, 造成内存的泄露, 所以, 应该在视图将要消失时关闭计时器, 在视图将要出现时启动计时器, 下面我们就拿代码进行分析:
/** 首先我们来模拟一个场景, 创建两个视图控制器(MainViewController, SecondController), 分别在两个视图控制器中创建两个button, 用模态方法进行相互跳转 */
/* 在MainViewController中 */
/** 创建button并添加点击事件, 点击跳转第二页 */
- (IBAction)buttonAction:(id)sender {
/** 模态进行跳转到第二界面 */
SecondViewController *second = [[SecondViewController alloc] init];
[self presentViewController:second animated:YES completion:^{
}];
[second release];
}
/** 在SecondController */
/** 定义Nstimer属性 */
@interface SecondViewController ()
@property (nonatomic, retain)NSTimer *timer;
@end
/** 先创建NStimer对象, 在 - (void)viewDidLoad中调用其创建方法 */
- (void)createTimer
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction
{
NSLog(@"hello");
}
/** 再创建button并添加点击事件, 点击跳转到第一界面 */
- (void)buttonAction
{
[self dismissViewControllerAnimated:YES completion:nil];
}
/** 在该方法中输出一串字符串, 如果self或timer被释放就一定会走该方法 */
- (void)dealloc
{
NSLog(@"self别释放");
[_timer release];
[super dealloc];
}
// 在该方法中将计时器取消
- (void)viewWillDisappear:(BOOL)animated
{
[self.timer invalidate];
sel.timer = nil;
}
/** 当你按照上述的方法建立场景后, 点击MainViewController中的button后跳转第二页面后, timer就会自动执行, 当点击SecondController中的button时就会跳转到第一界面, 但这时你就会发现, timer仍然继续循环执行, 并没有停止.
为什么会出现这种情况呢? 问题的关键就是我们在为在为NSTimer对象指定target的self(视图控制器本身对象)被强引用了, self的引用计数加1, 所以在我们在第二界面模态回第一界面时吗self不会被释放掉, 为了验证self这时是否被释放掉, 我们可以再 - (void)dealloc方法中打断点或输出一串字符串.
那么怎样解决这个问题呢? 我们可以在 - (void)viewWillDisappear:(BOOL)animated 视图将要消失的方法中掉用 - (void)invalidate 方法来取消计时器. 这样我们就解决了内存泄露问题了.
*/
NSTimer的属性及方法说明
>
方法
- (void)fire: 启动计时器, 即使计时器的完整循环没有完成, 同样可以触发
- (void)invalidate: 关闭计时器, 计时器会失效
属性
valid : 类型是BOOL, 属性判断计时器是否有效
timeInterval: 计时器每次的循环的时间间隔, 当计时器失效时会返回0;
userInfo: 用户信息
tolerance: 通过这个属性来设置计时器的误差范围
fireDate: 用这个属性来设置定时器的启动和停止时间,
用 - (void)invalidate方法和fireDate属性来关闭计时器的区别:
用方法关闭, 计时器会失效, 而用属性关闭计时器且可以再次启动, 计时器不会失效且计时器不会再次启动, 大家可以通过valid属性进行验证.
用 - (void)fire方法和fireDate属性启动计时器的区别:
用方法启动, 计时器只会执行一次循环, 而用属性启动, 计时器会继续循环执行.
下面我们就用代码来验证上述的两个区别
/** 在视图控制器MainViewController中创建两个button, 并添加点击事件, 用来控制timer */
/* 第一个button的点击事件 */
- (void)buttonAction:(id)sender {
#if 0
// 关闭计时器
[self.timer invalidate];
if (self.timer.valid == YES) {
NSLog(@"计时器有效");
}
else {
NSLog(@"计时器无效");
}
/** 输出: 计时器无效. 这时计时器无法启动 */
#endif
#if 1
// 停止计时器, 只有用该方法停止计时器, 计时器才可以重新被启动
self.timer.fireDate = [NSDate distantFuture];
if (self.timer.valid == YES) {
NSLog(@"计时器有效");
}
else {
NSLog(@"计时器无效");
}
/** 输出: 计时器有效, 这时计时器可以再次启动 */
#endif
}
/** 第二个button的点击事件 */
- (void)buttonAction1:(id)sender {
#if 0
// 启动计时器, 计时器从新启动, 继续循环
self.timer.fireDate = [NSDate distantPast];
#endif
#if 1
// 启动计时器, 但计时器只执行一次循环
[self.timer fire];
#endif
}
API 官方注释(英文)
/**
* @brief Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode
*
* @param <seconds> 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.
*
* @param <invocation> The invocation to use when the timer fires. The invocation object maintains a strong reference to its arguments until the timer is invalidated.
*
* @param <repeats> If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
*/
/** 方法 */
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats
/**
* @brief Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode.
*
* @param <seconds> 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.
*
* @param <target> The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
*
* @param <aSelector> The message to send to target when the timer fires.
*
* @param <userInfo> The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.
*
* @param <repeats> If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
*/
/** 方法 */
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
/**
* @brief Creates and returns a new NSTimer object initialized with the specified invocation object.
*
* @param <seconds> 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.
*
* @param <invocation> The invocation to use when the timer fires. The invocation object maintains a strong reference to its arguments until the timer is invalidated.
*
* @param <repeats> If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
*/
/** 方法 */
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats
/**
* @brief Creates and returns a new NSTimer object initialized with the specified object and selector.
*
* @param <seconds> 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.
*
* @param <target> The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
*
* @param <aSelector> The message to send to target when the timer fires.
*
* @param <userInfo> The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.
*
* @param <repeats> If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
*/
/** 方法 */
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
/**
* @brief Initializes a new NSTimer object using the specified object and selector
*
* @param <date> The time at which the timer should first fire.
*
* @param <target> The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
*
* @param <aSelector> The message to send to target when the timer fires.
*
* @param <userInfo> The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.
*
* @param <repeats> If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
*/
/** 方法 */
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats