iOS_NSTimer的那些事(二)

说明:

这个类对于大家并不陌生, 每当提到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];
对于这五种创建方法的总结:
  1. 参数repeats: repeats的类型是BOOL, 用来指定是否循环执行, 若设置为NO执行一次, 若设置为YES程序将会循环执行

  2. 参数userInfo: 用来设置用户的信息, 可以为空

  3. 对于用timerWith方法创建的对象, 必须手动用addTimer: forMode方法将NSTimer对象手动添加到循环池中, 并且手动启动计时器, 否则计时器将不会启动.

  4. 对于用scheduled方法创建的对象, 不需要手动添加和启动, 系统会帮我们完成的,

  5. 对于用initWith方法创建的对象, 需要手动加入循环池,但不需要手动启动计时器.

  6. 注意: 为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: 关闭计时器, 计时器会失效
属性
  1. valid : 类型是BOOL, 属性判断计时器是否有效

  2. timeInterval: 计时器每次的循环的时间间隔, 当计时器失效时会返回0;

  3. userInfo: 用户信息

  4. tolerance: 通过这个属性来设置计时器的误差范围

  5. 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值