干货!Swift/OC一个倒计时.计时器
2017-11-29 本文已影响196人
66b6d3e5fc98
计时器案例场景:短信验证码、商品抢购倒计时……
实现目标:
- 使用方便,高内聚低耦合高度模块化
- 不重复触发,重复触发不干扰
- 一经触发,多处可用
- 界面销毁依然有效
- 应用挂入后台依然有效
Swift版本
- 一个单例记录标签
class CD_CountDown {
fileprivate static let sharedInstance = CD_CountDown()
fileprivate init() {}
//提供静态访问方法
open static var shared: CD_CountDown {
return self.sharedInstance
}
///时间倒计时标识存储,预防重复创建同一计时线程
var notifyNames:[String] = []
}
- class 方法,方便调用
extension CD_CountDown {
//MARK:--- 时间倒计时 ----------
///时间倒计时
open class func addCountDown(_ notifyName:String, nowTimestamp:TimeInterval, endTimestamp:TimeInterval, second:Double = 0.1) {
//以notifyName为标识,不必重复创建线程
if CD_CountDown.shared.notifyNames.contains(notifyName) {return}
CD_CountDown.shared.notifyNames.append(notifyName)
//截止日期
let endDate:Date = Date(timeIntervalSince1970: endTimestamp)
//当前时间
let nowDate:Date = Date(timeIntervalSince1970: nowTimestamp)
//当前时间与系统时间差
let nowDateDiffer:TimeInterval = nowDate.timeIntervalSinceNow
//时间间隔 ^ 毫秒
var aTime:TimeInterval = endDate.timeIntervalSince(Date()) * 10
var bTime:Int = 999
var saveSecond:Int = 59
// 创建一个时间源
let queue = DispatchQueue(label: notifyName)
let timer = DispatchSource.makeTimerSource(queue:queue)
//循环执行,马上开始,间隔为1ms
timer.schedule(deadline: .now(), repeating: .milliseconds(Int(second*1000)))
// 设定时间源的触发事件
timer.setEventHandler(handler: {
//计算剩余时间
let gregorian = Calendar(identifier: .gregorian)
let cmps = gregorian.dateComponents([Calendar.Component.year,Calendar.Component.month,Calendar.Component.day,Calendar.Component.hour,Calendar.Component.minute,Calendar.Component.second], from: Date(timeIntervalSince1970: Date().timeIntervalSince1970 + nowDateDiffer), to: endDate)
aTime = endDate.timeIntervalSince(Date(timeIntervalSince1970: Date().timeIntervalSince1970 + nowDateDiffer)) * 10
bTime = saveSecond != cmps.second ? 999 : (bTime <= 0 ? 999 : bTime - Int(second*1000))
saveSecond = cmps.second ?? 59
if aTime <= 0 {
timer.cancel()
DispatchQueue.main.async(execute: {
//print("通知\(aTime)毫秒")
CD_CountDown.shared.notifyNames.remove(at: CD_CountDown.shared.notifyNames.index(of: notifyName)!)
NotificationCenter.default.post(name: NSNotification.Name.init(notifyName), object: M_CD_CountDownTime(year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, millisecond: 0))
})
} else {
DispatchQueue.main.async(execute: {
//print("通知\(aTime)毫秒")
NotificationCenter.default.post(name: NSNotification.Name.init(notifyName), object: M_CD_CountDownTime(year: cmps.year ?? 0, month: cmps.month ?? 0, day: cmps.day ?? 0, hour: cmps.hour ?? 0, minute: cmps.minute ?? 0, second: cmps.second ?? 0, millisecond: bTime*10+cTime))
})
}
})
// 启动时间源
timer.resume()
}
}
extension CD_CountDown {
//MARK:--- 验证码秒表倒计时 ----------
///验证码秒表倒计时
open class func addVerifyCode(_ notifyName:String, maxTime: Int = 60) {
//以notifyName为标识,不必重复创建线程
if CD_CountDown.shared.notifyNames.contains(notifyName) {return}
CD_CountDown.shared.notifyNames.append(notifyName)
let toTime = Date().timeIntervalSince1970 + TimeInterval(maxTime)
var aTime = maxTime
let queue = DispatchQueue(label: notifyName)
// 创建一个时间源
let timer = DispatchSource.makeTimerSource(queue:queue)
//循环执行,马上开始,间隔为0.1s,误差允许10微秒
timer.scheduleRepeating(deadline: .now(), interval: .seconds(1), leeway: .milliseconds(10))
// 设定时间源的触发事件
timer.setEventHandler(handler: {
aTime = Int(toTime - Date().timeIntervalSince1970)
if aTime <= 0 {
//timer.suspend()
timer.cancel()
DispatchQueue.main.async {
print("通知\(aTime)")
CD_CountDown.shared.notifyNames.remove(at: CD_CountDown.shared.notifyNames.index(of: notifyName)!)
NotificationCenter.default.post(name: NSNotification.Name.init(notifyName), object: 0)
}
}else{
DispatchQueue.main.async {
print("通知\(aTime)")
NotificationCenter.default.post(name: NSNotification.Name.init(notifyName), object: aTime)
}
}
})
// 启动时间源
timer.resume()
}
}
- 通知obj —— model
struct M_CD_CountDownTime {
var year:Int = 0
var month:Int = 0
var day:Int = 0
var hour:Int = 0
var minute:Int = 0
var second:Int = 0
var millisecond:Int = 0
}
- 使用
//验证码秒表倒计时
CD_CountDown.addVerifyCode("VerificationCodeName1", maxTime: 10)
cd_Notify.rx
.notification(Notification.Name(rawValue:"VerificationCodeName1"))
.subscribe(onNext: { [weak self](n) in
//print("收到通知\(String(describing: n.object))")
self?.upBtn(self!.btn_1, time: n.object as! Int)
}).disposed(by:disposeBag)
//日期倒计时
CD_CountDown.addCountDown("CD_CountDownNotificationName2030", nowTimestamp: Double(Date().timeIntervalSince1970 + 10.1), endTimestamp: 1893427200)
cd_Notify.rx
.notification(Notification.Name(rawValue:"CD_CountDownNotificationName2030"))
.subscribe(onNext: { [weak self](n) in
//print("收到通知\(String(describing: n.object))")
guard let m = n.object as? M_CD_CountDownTime else{return}
self?.lab_time.text = String(format: "%d年%.2d月%.2d日 %.2d:%.2d:%.2d.%.2d", m.year,m.month,m.day,m.hour,m.minute,m.second,m.millisecond)
}).disposed(by:disposeBag)
OC版本
- 一个单例记录标签
- class 方法,方便调用
//.h
@interface CD_CountDown : NSObject
#pragma mark ----- 单例
@property(nonatomic,strong) NSString *name;
+ (CD_CountDown*)shared;
@property(nonatomic,strong) NSMutableArray* notifyNames;
+ (void)addCountDown:(NSString*)notifyName nowTimestamp:(NSTimeInterval)nowTimestamp endTimestamp:(NSTimeInterval)endTimestamp second:(NSTimeInterval)second;
+ (void)addVerifyCode:(NSString*)notifyName maxTime:(NSInteger)maxTime;
@end
//.m
static CD_CountDown* shared = nil;
@implementation CD_CountDown
#pragma mark ----- 单例 -----
+ (CD_CountDown*) shared
{
static dispatch_once_t once;
dispatch_once(&once, ^{
if (shared == nil) {
shared = [[self alloc] init];
}
});
return shared;
}
/**
覆盖该方法主要确保当用户通过[[Singleton alloc] init]创建对象时对象的唯一性,alloc方法会调用该方法,只不过zone参数默认为nil,因该类覆盖了allocWithZone方法,所以只能通过其父类分配内存,即[super allocWithZone:zone]
*/
+(id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t token;
dispatch_once(&token, ^{
if(shared == nil){
shared = [super allocWithZone:zone];
}
});
return shared;
}
//自定义初始化方法,本例中只有name这一属性
- (instancetype)init
{
self = [super init];
if(self){
self.name = @"CD_CountDown";
self.notifyNames = [NSMutableArray array];
}
return self;
}
//覆盖该方法主要确保当用户通过copy方法产生对象时对象的唯一性
- (id)copy
{
return self;
}
//覆盖该方法主要确保当用户通过mutableCopy方法产生对象时对象的唯一性
- (id)mutableCopy
{
return self;
}
//自定义描述信息,用于log详细打印
- (NSString *)description
{
return [NSString stringWithFormat:@"memeory address:%p,property name:%@",self,self.name];
}
#pragma mark ----- 时间倒计时 -----
+ (void)addCountDown:(NSString*)notifyName nowTimestamp:(NSTimeInterval)nowTimestamp endTimestamp:(NSTimeInterval)endTimestamp second:(NSTimeInterval)second{
//以notifyName为标识,不必重复创建线程
if ([[[CD_CountDown shared] notifyNames] containsObject:notifyName]) {
return;
};
[[[CD_CountDown shared] notifyNames] addObject:notifyName];
//[[NSProcessInfo processInfo] systemUptime];开机到现在的时间
//创建一个队列
const char * lab = [[NSString stringWithFormat:@"%@.queue",notifyName] UTF8String];
dispatch_queue_t queue = dispatch_queue_create(lab, DISPATCH_QUEUE_CONCURRENT);
//创建一个timer放到队列里面
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置首次执行时间,时间间隔,精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, second*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
//截止日期
NSDate* endDate = [NSDate dateWithTimeIntervalSince1970:endTimestamp];
//当前时间
NSDate* nowDate = [NSDate dateWithTimeIntervalSince1970:nowTimestamp];
//当前时间与系统时间差
NSTimeInterval nowDateDiffer = nowDate.timeIntervalSinceNow;
//时间间隔 ^ 毫秒
__block NSTimeInterval aTime = nowDateDiffer * 10;
__block NSTimeInterval bTime = 999;
__block NSInteger saveSecond = 59;
//设置timer执行事件
dispatch_source_set_event_handler(timer, ^{
//计算剩余时间
NSDate* fromDate = [NSDate dateWithTimeIntervalSince1970:[NSDate date].timeIntervalSince1970 + nowDateDiffer];
NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian];
NSDateComponents * cmps = [gregorian components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond
fromDate:fromDate toDate:endDate options:0];
aTime = [endDate timeIntervalSinceDate:fromDate] * 10;
//时间差
bTime = saveSecond != cmps.second ? 999 : (bTime <= 0 ? 999 : bTime - Int(second*1000))
saveSecond = cmps.second;
if (aTime <= 0) {
//倒计时结束,关闭
dispatch_source_cancel(timer);
dispatch_async(dispatch_get_main_queue(), ^{
[[[CD_CountDown shared] notifyNames] removeObject:notifyName];
M_CD_CountDown* mo = [[M_CD_CountDown alloc] init];
mo.year = 0;
mo.month = 0;
mo.day = 0;
mo.hour = 0;
mo.minute = 0;
mo.second = 0;
mo.msecond = 0;
[[NSNotificationCenter defaultCenter] postNotificationName:notifyName object:mo];
});
}else{
dispatch_async(dispatch_get_main_queue(), ^{
//NSLog(@"发送通知%ld",(long)bTime);
M_CD_CountDown* mo = [[M_CD_CountDown alloc] init];
mo.year = cmps.year;
mo.month = cmps.month;
mo.day = cmps.day;
mo.hour = cmps.hour;
mo.minute = cmps.minute;
mo.second = cmps.second;
mo.msecond = bTime*10 + cTime;
[[NSNotificationCenter defaultCenter] postNotificationName:notifyName object:mo];
});
}
});
dispatch_resume(timer);
}
#pragma mark ----- 添加验证码计时队列 -----
+ (void)addVerifyCode:(NSString*)notifyName maxTime:(NSInteger)maxTime{
//以notifyName为标识,不必重复创建线程
if ([[[CD_CountDown shared] notifyNames] containsObject:notifyName]) {
return;
};
[[[CD_CountDown shared] notifyNames] addObject:notifyName];
/*
使用 系统时间 toTime - [NSDate date].timeIntervalSince1970
0即使程序挂入后台依然能正确倒计时
1,获取当前系统时间,在此基础上加上 maxTime,作为结束时间。
2,没间隔一秒获取当前系统时间,结束时间 - 当前系统时间 = 秒数。
*/
__block NSInteger toTime = [NSDate date].timeIntervalSince1970 + maxTime;
__block NSInteger aTime = maxTime;
//创建一个队列
const char * lab = [[NSString stringWithFormat:@"%@.queue",notifyName] UTF8String];
//dispatch_queue_create(lab, DISPATCH_QUEUE_CONCURRENT);
//dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create(lab, DISPATCH_QUEUE_CONCURRENT);
//创建一个timer放到队列里面
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置首次执行时间,时间间隔,精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
//设置timer执行事件
dispatch_source_set_event_handler(timer, ^{
//时间差
aTime = toTime - [NSDate date].timeIntervalSince1970;
if (aTime <= 0) {
//倒计时结束,关闭
dispatch_source_cancel(timer);
dispatch_async(dispatch_get_main_queue(), ^{
[[[CD_CountDown shared] notifyNames] removeObject:notifyName];
[[NSNotificationCenter defaultCenter] postNotificationName:notifyName object:@"0"];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
//NSLog(@"发送通知%ld",(long)bTime);
[[NSNotificationCenter defaultCenter] postNotificationName:notifyName object:[NSString stringWithFormat:@"%ld",(long)aTime]];
});
}
});
dispatch_resume(timer);
}
@end
- 通知obj —— model
@interface M_CD_CountDown : NSObject
@property(nonatomic,assign) NSInteger year;
@property(nonatomic,assign) NSInteger month;
@property(nonatomic,assign) NSInteger day;
@property(nonatomic,assign) NSInteger hour;
@property(nonatomic,assign) NSInteger minute;
@property(nonatomic,assign) NSInteger second;
@property(nonatomic,assign) NSInteger msecond;
@end
- 使用
//验证码秒表倒计时
[CD_CountDown addVerifyCode:@"VerificationCodeName1" maxTime:10];
[[[[CD_Const cd_Notify] rac_addObserverForName:@"VerificationCodeName1" object:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(NSNotification * x) {
[weakSelf updataBtn:weakSelf.btn_1 time:x.object];
}];
//日期倒计时
[CD_CountDown addCountDown:@"CD_CountDownName2030" nowTimestamp:[NSDate date].timeIntervalSince1970 endTimestamp:1893427200 second:0.1];
[[[[CD_Const cd_Notify] rac_addObserverForName:@"CD_CountDownName2030" object:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(NSNotification * x) {
M_CD_CountDown* mo = x.object;
weakSelf.lab_Time.text = [NSString stringWithFormat:@"%.2ld年%.2ld月%.2ld日 %.2ld:%.2ld:%.2ld.%.2ld",mo.year,mo.month,mo.day,mo.hour,mo.minute,mo.second,mo.msecond];
}];