NSTimer详解----使用、保留环问题、与runloop的关系

来源:互联网 发布:坚持不下去的时候 知乎 编辑:程序博客网 时间:2024/04/19 21:22

一、使用NSTimer你需要了解的内容
(1)只有将计时器放在运行循环中,它才能正常的触发任务。
(2)NSTimer对象会保留target,直到计时器失效,调用invalidate可令其失效;一次性计时器触发完就失效
(3)反复执行的timer容易造成保留环。
(4)可以使用分类,用block打破保留环,后面会具体介绍
iOS 10之后引入新方法,可以得到timer弱引用避免保留环

__weak MyClass* weakSelf = self;_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer* t) {    MyClass* _Nullable strongSelf = weakSelf;    [strongSelf doSomething];}];

(5)NSTimer的实时性
无论是单次执行的NSTimer还是重复执行的NSTimer都不是准时的,这与当前NSTimer所处的线程有很大的关系,如果NSTimer当前所处的线程正在进行大数据处理(假设为一个大循环),NSTimer本次执行会等到这个大数据处理完毕之后才会继续执行。

这期间有可能会错过很多次NSTimer的循环周期,但是NSTimer并不会将前面错过的执行次数在后面都执行一遍,而是继续执行后面的循环,也就是在一个循环周期内只会执行一次循环。

无论循环延迟的多离谱,循环间隔都不会发生变化,在进行完大数据处理之后,有可能会立即执行一次NSTimer循环,但是后面的循环间隔始终和第一次添加循环时的间隔相同。

二、相关API介绍

//使用下面两个方法创建timer,你需要手动显示将timer添加进自定义的runloop中timerWithTimeInterval:invocation:repeats:timerWithTimeInterval:target:selector:userInfo:repeats//使用下面两个函数,timer会默认添加到当前runloop或者默认runloop中scheduledTimerWithTimeInterval:invocation:repeats:scheduledTimerWithTimeInterval:target:selector:userInfo:repeats://这里分析最后一个函数:这个方法创建出来的计时器可以在一段时间后执行任务,也可以令其重复执行任务;计时器会自动保留对象,等到任务执行完毕再释放对象。调用invalidate可以使timer失效;执行完任务后,一次性的timer会失效。如果将计时器设置为重复执行的模式,必须手动调用invalidate使其停止//触发timer- (void)fire;//使timer无效(void)invalidate;@property (copy) NSDate *fireDate;@property (readonly) NSTimeInterval timeInterval;//下面是iOS10之后引入的方法+ (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));//添加到当前runloop的默认model中- (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));

三、简单使用

@interface    NSTimer *autoTimer;@implementation// Start timer    autoTimer = [NSTimer scheduledTimerWithTimeInterval:(3.0)        target:self         selector:@selector(autoTimerFired:)         userInfo:nil         repeats:YES];// Stop timer:    [autoTimer invalidate];    autoTimer = nil;

四、timer与runloop
(1)timer为什么要加入runloop?
NSTimer其实也是一种资源,如果看过多线程变成指引文档的话,我们会发现所有的source如果要起作用,就得加到runloop中去。同理timer这种资源要想起作用,那肯定也需要加到runloop中才会又效喽。如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下main thread的runloop,你会发现有很多资源。

NSTimer实例总是需要在运行循环上进行调度才能正常运行。如果您从主线程中执行此操作,则可以使用scheduleTimerWithTimeInterval,它将自动添加到主运行循环,无需手动调用NSRunLoop方法addTimer。如果需要的话,可以创建计时器并自行添加。
如果您从后台线程创建一个没有自己的运行循环的计时器(默认情况下,当您使用后台调度队列或运行队列时,正在运行的线程将不具有自己的运行循环)然后您必须手动将计时器添加到运行循环。

或者,如果您真的希望计时器在后台线程上运行,而不是为该线程创建运行循环并将计时器添加到新的运行循环,则可以使用GCD调度计时器,它不需要runloop。有关Objective-C示例
简单的英文大家应该可以理解的……^_^
If you want a repeating timer to be invoked on a dispatch_queue_t, use dispatch_source_create with a DISPATCH_SOURCE_TYPE_TIMER:

dispatch_queue_t  queue = dispatch_queue_create("com.firm.app.timer", 0);dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 20ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);dispatch_source_set_event_handler(timer, ^{    // stuff performed on background queue goes here    NSLog(@"done on custom background queue");    // if you need to also do any UI updates, synchronize model updates,    // or the like, dispatch that back to the main queue:    dispatch_async(dispatch_get_main_queue(), ^{        NSLog(@"done on main queue");    });});dispatch_resume(timer);

That creates a timer that runs once every 20 seconds (3rd parameter to dispatch_source_set_timer), with a leeway of a one second (4th parameter to dispatch_source_set_timer).
To cancel this timer, use dispatch_source_cancel:
dispatch_source_cancel(timer);

因此,除非在后台线程中创建定时器,否则只需使用scheduledTimerWithTimeInterval,而不必担心手动将其添加到运行循环中

(2)runloop 的mode的切换对timer的影响
以实际例子说明

// 0.没有设置runloop模式的方式  //    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];  // 1.创建NSTimer  NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];  // 2.1.添加到runloop  // 把定时器添加到当前的runloop中,并选择默认运行模式  // kCFRunLoopDefaultMode == NSDefaultRunLoopMode  // 但是这种模式下如果拖拽界面,runloop会自动进入UITrackingMode,优先于定时器追踪模式  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];  // 2.2.我们更改一下模式UITrackingRunLoopMode  // 当runloop的模式是UITrackingRunLoopMode时定时器才工作  [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];  // 2.3.还有一种runloop的mode,占位运行模式  // 就可以无论是界面追踪还是普通模式都可以运行  /**  common modes = <CFBasicHash 0x7fb7424021b0 [0x10f12f7b0]>{type = mutable set, count = 2,  entries =>  0 : <CFString 0x11002a270 [0x10f12f7b0]>{contents = "UITrackingRunLoopMode"}  2 : <CFString 0x10f14fb60 [0x10f12f7b0]>{contents = "kCFRunLoopDefaultMode"}  */  /**  NSTimer的问题,NSTimer是runloop的一个触发源,由于NSTimer是添加到runloop中使用的,所以如果只添加到default模式,会导致拖拽界面的时候runloop进入界面跟踪模式而定时器暂停运行,不拖拽又恢复的问题,这时候就应该使用runloop的NSRunLoopCommonModes模式,能够让定时器在runloop两种模式切换时也不受影响。  */  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];  

五、NSTimer保留环问题

    1   @interface NSTimer (EOCBlocksSupport)      2         3   + (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval      4                                             block:(void(^)())block      5                                           repeats:(BOOL)repeats;      6         7   @end      8         9   @implementation NSTimer (EOCBlocksSupport)      10        11  + (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval      12                                            block:(void(^)())block      13                                          repeats:(BOOL)repeats      14  {      15      return [self scheduledTimerWithTimeInterval:interval      16                                           target:self      17                                         selector:@selector(eoc_blockInvoke:)      18                                         userInfo:[block copy]      19                                          repeats:repeats];      20  }      21        22  + (void)eoc_blockInvoke:(NSTimer*)timer {      23      void (^block)() = timer.userInfo;      24      if (block) {      25          block();      26      }      27  } 

在xxxViewContoler里面使用这个扩展。

-(void)startPolling {
 __weak EOCClass * weakself  = self;
  _pollTimer =  eoc_scheduledTimerWithTimeInterval:5.0       block:^ {    EOCClass *strongSelf = weakself;    //上边代码也可以用__strong来修饰强关系,这里面应该是变为autoReleaseing的了。可以保证在这个作用于范围使用。    [strongSelf doReFresh];    }     repeats:(BOOL)YES;}

此处使用__weak还能让程序更加安全,倘若开发者在delloc的时候忘记调用invalidate,从而使定时器在运行【这里就有疑问了,xxxcontroller持有timer,当控制器回收时timer也应该引用数为0啊,原因是这里runloop还会持有一份引用。】,倘若这样的事情发生,weakself会变为nil。


下面整理的stack overflow上的问题,对于理解NSTimeryou

疑问
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES];
这句代码,NSTimer将保留target造成保留环,(如果使用timer的实例为A,当指向A实例的最后一个引用移走后,A实例不会被销毁因为timer还保留着它(这时timer重复执行),而timer也不会释放因为A引用着它,所以A实例永久的存在,也就是内存泄漏了),我了解timer必须invalidated,那么我在dealloc方法中停用timer可以避免,对吗?

解答1:在dealloc中无效定时器是无用的(在这里):定时器保持对其目标的强烈引用。这意味着只要定时器保持有效,其目标将不会被释放。作为推论,这意味着定时器的目标在其dealloc方法中尝试使定时器无效是没有意义的,只要定时器有效,dealloc方法将不被调用。

针对解答2:
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];

具有以下作用:(i)对self有一个弱应用; (ii)读取弱引用来提供指向NSTimer的指针。 它不会产生具有弱引用的NSTimer的效果。 该代码和使用__strong引用之间的唯一区别是,如果self被释放,那么你将传递nil给定时器。

拓展---stack overflow上提供的一种使用GCD的API事项的重复执行

- (void) doSomethingRepeatedly{    // Do it once    NSLog(@"doing something …");    // Repeat it in 2.0 seconds    __weak typeof(self) weakSelf = self;    double delayInSeconds = 2.0;    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){        [weakSelf doSomethingRepeatedly];    });}
0 0
原创粉丝点击