iOS开发-NSTimer

配图

1.初始化

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

以上初始化方法将以默认mode直接添加到当前的runloop中。

+ (NSTimer *)timerWithTimeInterval:(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;

不带scheduled的初始化方法需要手动addTimer:forMode: 将timer添加到一个runloop中。

 /**
  *  创建一个timer , 并将它添加到当前线程的RunLoop
  */
timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:@{@"key":@"value"} repeats:true];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

关于runloop及mode相关问题可以先看看这篇文章深入理解RunLoop

2.触发及销毁

  • fire

You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

cmd+shift+0打开官方文档,是说在重复执行的定时器中调用此方法后立即触发该定时器,但不会中断其之前的执行计划;在不重复执行的定时器中调用此方法,立即触发后,就会使这个定时器失效。比如我有个5秒的重复执行的定时器,不调用fire方法,定时器第一次执行方法是在5秒之后,如果调用fire方法则会立即执行。

  • invalidate

Stops the receiver from ever firing again and requests its removal from its run loop.
This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

这个是唯一一个可以将计时器从runloop中移出的方法,没有正确的使用该方法可能会导致定时器对象无法被释放,所以就有了下面探讨的问题。

NSTimer.png

3.循环引用问题

1.什么时候会出现循环引用?

我所理解的循环引用是两个对象相互强引用,或者多个对象强引用形成一个封闭的环。对象A强引用B,B再强引用A;或者A强引用B,B强引用C,C再强引用A...
接下来咱们回到NSTimer,首先你要明白几点细节:

  • timer会被添加到runloop中,否则不会运行,当然添加的runloop不存在也不会运行。
  • timer需要添加到runloop的一个或多个模式中,模式不对也不会运行。
  • runloop会对timer进行强引用,timer会对目标对象target进行强引用。
    看到最后一点应该差不多清晰了,当timer作为一个属性或者成员变量时,是被self强引用的,通常timer的目标target都是self,这个时候就导致循环引用了。

2.如何解决?

其实解决timer的循环引用,我们首先想到的应该是在合适的时机调用timer的invalidate方法,所以你可能会这样做

- (void)dealloc
{
    [_timer invalidate];
    _timer = nil;
}

但是在调delloc方法之前需要先释放定时器,而释放定时器又要走delloc方法,这显然是矛盾的。那再换一种思路

/**
 *在视图控制器视图消失的方法中释放定时器,
 *但是如果还有下一级新的控制器,则每次进入这个页面时都要重新添加定时器
 */
- (void)viewDidDisappear:(BOOL)animated
{
    [_timer invalidate];
    _timer = nil;
}

上面的确可以解决循环引用的问题,但不推荐,原因看注释。推荐下面的方法:

  • 引入中间类(一切问题都可以通过中间层解决)
//.h文件
@interface DXTimer : NSObject
- (void)cleanTimer;
@end

//.m文件
#import "DXTimer.h"
@interface DXTimer()
@property (nonatomic,strong) NSTimer *timer;
@end
@implementation DXTimer
- (instancetype)init
{
    if (self = [super init]) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeOut:) userInfo:nil repeats:YES];
    }
    return self;
}
- (void)timeOut:(NSTimer *)timer
{
    NSLog(@"我还在呀...");
}
- (void)cleanTimer
{
    [_timer invalidate];
    _timer = nil;
}
//控制器中初始化中间类从而间接初始化定时器
- (void)testMyTimer
{
    _myTimer = [[DXTimer alloc] init];
}

/**
 *引入中间类时,只需要在delloc方法中调用中间类的释放定时器的方法
 */
- (void)dealloc
{
    [_myTimer cleanTimer];
}

引入DXTimer中间类后,引用关系是这样的,控制器强引用DXTimerDXTimer强引用timer,timer强引用DXTimer,控制器跟timer不存在相互引用关系,在控制器的delloc方法中调用DXTimercleanTimer方法释放定时器,从而使DXTimer也被释放。

  • 使用block

首先实现一个NSTimer的分类:

#import "NSTimer+Block.h"

@implementation NSTimer (Block)
+ (NSTimer *)dx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                       repeats:(BOOL)repeats
                                         block:(void (^)())block
{
    return [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(dx_blockInvoke:) userInfo:[block copy] repeats:repeats];
}

+ (void)dx_blockInvoke:(NSTimer *)timer
{
    void (^block)(NSTimer *timer) = timer.userInfo;
    
    if (block) {
        block(timer);
    }
}
@end

控制器中初始化定时器:

//定义了一个__weak的self_weak_变量
#define weakifySelf  \
__weak __typeof(&*self)weakSelf = self

//局域定义了一个__strong的self指针指向self_weak
#define strongifySelf \
__strong __typeof(&*weakSelf)self = weakSelf

- (void)testBlockTimer
{
    weakifySelf;
    _blockTimer = [NSTimer dx_scheduledTimerWithTimeInterval:1 repeats:YES block:^{
        strongifySelf;
        [self timeOut];
    }];
}

- (void)timeOut
{
    NSLog(@"定时器还在...");
}

这里的weakifySelfstrongifySelf很关键,先定义了一个弱引用,令其指向self,然后使块捕获这个引用,而不直接去捕获普通的self变量。也就是说,self不会为计时器所保留。当块开始执行时,立刻生成strong引用,以保证实例在执行期间持续存活。

为避免循环引用的问题,iOS10定时器API新增了block方法,跟这里用分类实现的原理其实是差不多的,只是直接在原始类中新增方法。

+ (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));
+ (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));

关于定时器在面试中出现的频率还是挺高的,反正我好几次都被问到,所以决定写一篇总结。附上文章的一个简单demo代码NSTimer循环引用demo

如果觉得文章对你有帮助,请不吝给个赞,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 发生场景 在 Controller B 中有一个 NSTimer 你创建了它,并挂载到 main runloop ...
    ck2016阅读 8,603评论 9 18
  • 一直对NSTimer有过使用,但是没有研究过.但是这次发现, 出现了循环引用的现象,NSTimer并没有被释...
    3a169b0787bc阅读 450评论 0 1
  • 由于计时器会保留其目标对象,使用计时器时很容易引起循环引用,如下代码所示: 大多数开发者可能都会这样来实现定时器。...
    Levi_阅读 10,633评论 13 25
  • 文章以在TimerViewController中使用计时器为例,在VC中声明一个NSTimer属性。 创建NSTi...
    皓皓大帝阅读 10,669评论 11 40
  • 作者:阿黎 00 “我们结婚吧!” “不可以。” “为什么?” “我不想让你独守空房。” “你知道,我不比你轻松啊...
    阿黎Aria阅读 825评论 13 6