iOS_NSTimer的那些事(二)

来源:互联网 发布:大连海关数据分中心 编辑:程序博客网 时间:2024/05/01 09:39

说明:

这个类对于大家并不陌生, 每当提到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)repeats2 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats3 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats4 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats5 - (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
0 0