一、使用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];
    });
}