您的位置:首页 > 其它

NSTimer使用小记

2016-05-25 12:00 211 查看

转载自:https://www.mgenware.com/blog/?p=459


1. NSRunLoopCommonModes和Timer

    当使用
NSTimer
scheduledTimerWithTimeInterval
方法时。事实上此时Timer会被加入到当前线程的Run
Loop中,且模式是默认的
NSDefaultRunLoopMode
。而如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如
UIScrollView
的拖动操作,会将Run
Loop切换成
NSEventTrackingRunLoopMode
模式,在这个过程中,默认的
NSDefaultRunLoopMode
模式中注册的事件是不会被执行的。也就是说,此时使用
scheduledTimerWithTimeInterval
添加到Run
Loop中的Timer就不会执行。

 

    所以为了设置一个不被UI干扰的Timer,我们需要手动创建一个Timer,然后使用
NSRunLoop
addTimer:forMode:
方法来把Timer按照指定模式加入到Run
Loop中。这里使用的模式是:
NSRunLoopCommonModes
,这个模式等效于
NSDefaultRunLoopMode
NSEventTrackingRunLoopMode
的结合。(参考Apple文档

参考代码:

- (void)viewDidLoad
{
[super viewDidLoad];

NSLog(@"主线程 %@", [NSThread currentThread]);
//创建Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
//使用NSRunLoopCommonModes模式,把timer加入到当前Run Loop中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

//timer的回调方法
- (void)timer_callback
{
NSLog(@"Timer %@", [NSThread currentThread]);
}


 

输出:

主线程 <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}


 
返回目录

2. NSThread和Timer

    上面讲的
NSRunLoopCommonModes
和Timer中有一个问题,这个Timer本质上是在当前线程的Run
Loop中循环执行的,因此Timer的回调方法不是在另一个线程的。那么怎样在真正的多线程环境下运行一个Timer呢?

    可以先试试
NSThread
。同上,我们还是会把Timer加到Run
Loop中,只不过这个是在另一个线程中,因此我们需要手动执行Run Loop(通过
NSRunLoop
run
方法),同时注意在新的线程执行中加入
@autoreleasepool


完整代码如下:

- (void)viewDidLoad
{
[super viewDidLoad];

NSLog(@"主线程 %@", [NSThread currentThread]);

//创建并执行新的线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}

- (void)newThread
{
@autoreleasepool
{
//在当前Run Loop中添加timer,模式是默认的NSDefaultRunLoopMode
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
//开始执行新线程的Run Loop
[[NSRunLoop currentRunLoop] run];
}
}

//timer的回调方法
- (void)timer_callback
{
NSLog(@"Timer %@", [NSThread currentThread]);
}


 

输出:

主线程 <NSThread: 0x7118800>{name = (null), num = 1}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}
Timer <NSThread: 0x715c2e0>{name = (null), num = 3}


 
返回目录

3. GCD中的Timer

GCD中的Timer应该是最灵活的,而且是多线程的。GCD中的Timer是靠Dispatch Source来实现的。

因此先需要声明一个
dispatch_source_t
本地变量:

@interface ViewController ()
{
dispatch_source_t _timer;
}


 

接着通过
dispatch_source_create
函数来创建一个专门的Dispatch
Source,接着通过
dispatch_source_set_timer
函数来设置Timer的参数,注意这里的时间参数有些蛋疼。

开始时间的类型是
dispatch_time_t
,最好用
dispatch_time
或者
dispatch_walltime
函数来创建
dispatch_time_t
对象。如果需要Timer立即执行,可以传入
dispatch_time(DISPATCH_TIME_NOW,
0)


internal
leeway
参数分别表示Timer的间隔时间和精度。类型都是
uint64_t
。间隔时间的单位竟然是纳秒。可以借助预定义的
NSEC_PER_SEC
宏,比如如果间隔时间是两秒的话,那
interval
参数就是:
2
* NSEC_PER_SEC


leeway
就是精度参数,代表系统可以延时的时间间隔,最高精度当然就传0。

然后通过
dispatch_source_set_event_handler
函数来设置Dispatch
Source的事件回调,这里当然是使用Block了。

最后所有
dispatch_source_t
创建后默认都是暂停状态的,所以必须通过
dispatch_resume
函数来开始事件监听。这里就代表着开始Timer。

 

完整代码:

NSLog(@"主线程 %@", [NSThread currentThread]);
//间隔还是2秒
uint64_t interval = 2 * NSEC_PER_SEC;
//创建一个专门执行timer回调的GCD队列
dispatch_queue_t queue = dispatch_queue_create("my queue", 0);
//创建Timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//使用dispatch_source_set_timer函数设置timer参数
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
//设置回调
dispatch_source_set_event_handler(_timer, ^()
{
NSLog(@"Timer %@", [NSThread currentThread]);
});
//dispatch_source默认是Suspended状态,通过dispatch_resume函数开始它
dispatch_resume(_timer);


 

输出:

主线程 <NSThread: 0x711fab0>{name = (null), num = 1}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
Timer <NSThread: 0x713a380>{name = (null), num = 3}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: