废话不多说,gitHub 地址 内置两种实现方式创建timer,喜欢给个star咯~
-
一、前言
iOS 10 之后,NSTimer 多了三个类方法,可以实现block回调,大大简化了定时器的使用,但是在iOS 10 之前,都是要苦逼地去实现selector
+ (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));
- (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));
-
二、API分析
参考iOS 10 之后的新接口,只需要传三个参数就行,时间间隔和是否重复执行很容易传,那么block该怎么传进来执行呢?先看看iOS10 之前提供的接口,发现其实就是少了userInfo,因为通过block回调,因此就不需要userInfo进行传参了,所以我就猜想iOS10之后,苹果就是利用这个userInfo将block传递进来的,然后其他的不做任何处理(经本人测试发现,子线程中使用定时器,没开启子线程的运行循环,定时器依然没任何作用)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(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;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
-
三、实现方式:
-
1、按上述分析:通过NSTimer的userInfo传递block,实现block回调。
- 优点:系统封装好,使用简单快捷,代码少,可封装成一个NSTimer 的分类进行调用
- 缺点:需要依赖运行循环,子线程需要特殊处理(可再封装,内部处理,外界调用就不需要理运行循环,特殊情况例外)
-
2、通过GCD 自定义定时器,通过dispatch_source_set_event_handler 传递block,实现block回调
实现步骤(4步):(具体看代码实现)
dispatch_source_create ---> dispatch_source_set_timer --->dispatch_source_set_event_handler --->dispatch_resume
优点:不依赖运行循环,主、子线程照样使用
缺点:是否重复执行需要自己去判断实现,ARC情况下,dispatch_source_t创建的定时器对象会提前销毁,可利用block强引用它,不让其销毁,然后手动实现销毁,相对麻烦很多
-
四、具体实现:(具体方法的解释,代码中都有注释)
第一种实现:
#------------------------- h文件
/**
* @author 孔凡列, 16-10-11 07:10:28
*
* 创建定时器,不管子线程还是主线程直接创建,无需添加到运行循环或者开启运行循环
*
* @param interval 执行间隔
* @param repeat 是否重复执行block回调
* @param handle block回调
*
* @return 返回时间对象
*/
+ (NSTimer *)fl_timer:(NSTimeInterval)interval repeat:(BOOL)repeat handle:(void(^)(NSTimer *timer))handle;
#------------------------- m文件
+ (NSTimer *)fl_timer:(NSTimeInterval)interval repeat:(BOOL)repeat handle:(void(^)(NSTimer *timer))handle{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(operationBlock:) userInfo:handle repeats:repeat];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
if ([NSRunLoop currentRunLoop] != [NSRunLoop mainRunLoop]) {
/**
* @author 孔凡列, 16-09-21 08:09:06
*
* 子线程就开启当前线程的运行循环
*/
[[NSRunLoop currentRunLoop] run];
}
return timer;
}
+ (void)operationBlock:(NSTimer *)timer{
void(^block)(NSTimer *timer) = timer.userInfo;
block(timer);
}
第二种实现方式
#------------------------- h文件
/*
* author 孔凡列
*
* gitHub https://github.com/gitkong
* cocoaChina http://code.cocoachina.com/user/
* 简书 http://www.jianshu.com/users/fe5700cfb223/latest_articles
* QQ 279761135
* 喜欢就给个like 和 star 喔~
*/
#import <Foundation/Foundation.h>
@interface FLTimer : NSObject
/**
* @author 孔凡列, 16-10-11 07:10:28
*
* 创建定时器,主线程执行block,注意block强引用问题
*
* @param interval 执行间隔
* @param handler block回调
* @param repeat 是否重复执行block回调
*
* @return 返回时间对象
*/
+ (instancetype)fl_timer:(NSTimeInterval)interval handel:(void(^)(FLTimer *timer))handler repeat:(BOOL)repeat;
/**
* @author 孔凡列, 16-10-11 07:10:37
*
* 创建定时器,自定义线程执行block,注意block强引用问题
*
* @param interval 执行间隔
* @param queue 自定义县城
* @param handler block回调
* @param repeat 是否重复执行回调
*
* @return 返回时间对象
*/
+ (instancetype)fl_timer:(NSTimeInterval)interval queue:(dispatch_queue_t)queue handel:(void(^)(FLTimer *timer))handler repeat:(BOOL)repeat;
/**
* @author 孔凡列, 16-10-11 07:10:38
*
* 销毁定时器
*/
- (void)fl_invalidate;
@end
#------------------------- m文件
//
// FLTimer.m
// FLTimerDemo
//
// Created by clarence on 16/10/11.
// Copyright © 2016年 clarence. All rights reserved.
//
#import "FLTimer.h"
@interface FLTimer ()
/**
* @author 孔凡列, 16-09-21 08:09:06
*
* 保存timer对象
*/
@property (nonatomic,weak)dispatch_source_t timer;
@end
@implementation FLTimer
+ (instancetype)fl_timer:(NSTimeInterval)interval queue:(dispatch_queue_t)queue handel:(void(^)(FLTimer *timer))handler repeat:(BOOL)repeat{
FLTimer *fl_timer = [[self alloc] init];
// 创建定时器对象
__block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/**
* @author 孔凡列, 16-09-21 08:09:06
*
* internal和leeway参数分别表示Timer的间隔时间和精度。类型都是uint64_t。间隔时间的单位竟然是纳秒。可以借助预定义的NSEC_PER_SEC宏,比如如果间隔时间是两秒的话,那interval参数就是:2 * NSEC_PER_SEC。leeway就是精度参数,代表系统可以延时的时间间隔,最高精度当然就传0。
*/
// 内部计数器
__block NSTimeInterval counter;
// 设置时间间隔
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC, 0);
// 定时器回调
dispatch_source_set_event_handler(timer, ^{
/**
* @author 孔凡列, 16-09-21 08:09:06
*
* 关键一步:block强引用fl_timer,避免ARC情况下提前释放
*/
fl_timer.timer = timer;
// 实现重复执行回调
if (!repeat){
if (counter == interval) {
dispatch_source_cancel(timer);
timer = nil;
}
else{
counter ++;
handler(fl_timer);
}
}
else{
handler(fl_timer);
}
});
if (timer) {
// 开启定时任务
dispatch_resume(timer);
}
return fl_timer;
}
+ (instancetype)fl_timer:(NSTimeInterval)interval handel:(void(^)(FLTimer *timer))handler repeat:(BOOL)repeat{
return [self fl_timer:interval queue:dispatch_get_main_queue() handel:handler repeat:repeat];
}
- (void)fl_invalidate{
if(self.timer) {
// 挂起任务
dispatch_suspend(self.timer);
// 取消任务
dispatch_source_cancel(self.timer);
// 防止野指针
self.timer = nil;
}
}
@end