iOS 的倒计时有多种实现细节,Cocoa Touch 为我们提供了 NSTimer 类和 GCD 的dispatch_source_set_timer方法去更加方便的使用计时器。我们也可以很容易的的各种 UI 控件上添加倒计时功能,你只需

iOS 的倒计时有多种实现细节,Cocoa Touch 为我们提供了 NSTimer 类和 GCD 的dispatch_source_set_timer 方法去更加方便的使用计时器。我们也可以很容易的的各种 UI 控件上添加倒计时功能,你只需要定时刷新一次界面,给控件文本属性重新赋新值即可,但在实际项目中,可能并没有你想的这么简单美好。

我们不妨设想一下这样的情景:

主界面上有一个注册按钮,你点击按钮 push 到下一级页面,这个页面让你输入手机号并有一个获取验证码的按钮。你填完号码,再点击“获取验证码”按钮,然后按钮上的文字开始了 60 秒的倒计时。20 秒之后你 pop 回上一级页面,那么现在的页面应该被销毁了,10 秒后再次 push 到这个注册页面,那么倒计时按钮上的文字应该是【获取验证码】还是 【30 秒后重试】?

合理交互应该是:按钮正在从 30 秒开始,继续倒计时,直到秒数为 0 然后按钮文字再次变为【获取验证码】,并且倒计时过程中,按钮不可点击。否则倒计时限制将不再有意义。

有 2 个细节需要注意:

  • 界面销毁以后,计时器还在继续计时
  • 重新创建界面后,如果该按钮的计时器存在,则应当立即继续倒计时。

我们要做的就是 获取该按钮对应对的计时器 。

有人会说,何必如此麻烦,直接将这个页面或者这个按钮写成单例不就得了?是的,单例可以轻松解决这个问题,但是这种设计模式切不可滥用,假如你的 App 有20 个页面需要获取验证码按钮,那岂不是得生成 20 个单例的 View controller ?要知道,你并不是经常需要这些页面。如果把按钮设计成单例,那更不可取,一但你修改了一个按钮,其他地方的按钮必受牵连,引发不可估计的后果。

我的一个方案是, 生成一个全局的计时器管理类,它负责为每一个需要倒计时功能的按钮分配一个计时器,按钮和计时器通过一个 key 相互绑定。按钮被 dealloc 之后,计时器任然存在,直至计时结束。按钮重新生成时,计时器管理类会根据 key 决定该按钮是否需要继续倒计时 。

Do it!

首先需要思考,这个计时器管理类应该是是什么样子?它的具体功能又是什么?我给它命名为 WLButtonCountdownManager ,它是一个全局类,可用单例设计(1 个单例类比 20 个单例页面划算得多)。它负责分配计时器并将其与按钮绑定,所以它需要有一个容器属性来存储计时器,并且还要知道,容器里是否已经有计时器在跑了。那么 WLButtonCountdownManager 的头文件大概类似于这样:

NS_ASSUME_NONNULL_BEGIN
@interface WLButtonCountdownManager : NSObject /**
* 获取单例
*
* @return 该类的唯一实例
*/
+ (instancetype)defaultManager; /**
* 开始倒计时,如果倒计时管理器里具有相同的key,则直接开始回调。
*
* @param aKey 任务key,用于标示唯一性
* @param timeInterval 倒计时总时间,受操作系统后台时间限制,倒计时时间规定不得大于 120 秒.
* @param countingDown 倒计时时,会多次回调,提供当前秒数
* @param finished 倒计时结束时调用,提供当前秒数,值恒为 0
*/
- (void)scheduledCountDownWithKey:(NSString *)aKey
timeInterval:(NSTimeInterval)timeInterval
countingDown:(nullable void (^)(NSTimeInterval leftTimeInterval))countingDown
finished:(nullable void (^)(__unused NSTimeInterval finalTimeInterval))finished; /**
* 查询倒计时任务是否存在
*
* @param akey 任务key
* @param task 任务
* @return YES - 存在, NO - 不存在
*/
- (BOOL)coundownTaskExistWithKey:(NSString *)akey task:(NSOperation * _Nullable * _Nullable)task; @end
NS_ASSUME_NONNULL_END

[*注]这里不得不提一下,对于单例的写法,各位看官仁者见仁智者见智。一个严谨的单例至少需要满足以下条件:

  1. 全局唯一性 
    ①不可通过 alloc 再次分配资源 
    ②线程安全
  2. 不可继承

很多人忽略了第一条的第一点和第二条。

About timer

关于计时器,我使用子线程每秒睡眠一次进行模拟,计时操作精度要求并不高,且线程池也利于管理。

第一步,子类化一个 NSOperation 的类,名为 WLCountdownTask :

@interface WLCountdownTask : NSOperation
/**
* 计时中回调
*/
@property (copy, nonatomic) void (^countingDownBlcok)(NSTimeInterval timeInterval);
/**
* 计时结束后回调
*/
@property (copy, nonatomic) void (^finishedBlcok)(NSTimeInterval timeInterval);
/**
* 计时剩余时间
*/
@property (assign, nonatomic) NSTimeInterval leftTimeInterval;
/**
* 后台任务标识,确保程序进入后台依然能够计时
*/
@property (assign, nonatomic) UIBackgroundTaskIdentifier taskIdentifier;
@end - (void)main {
self.taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; while (--_leftTimeInterval > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
if (_countingDownBlcok) _countingDownBlcok(_leftTimeInterval);
}); [NSThread sleepForTimeInterval:1];
} dispatch_async(dispatch_get_main_queue(), ^{
if (_finishedBlcok) {
_finishedBlcok(0);
}
}); if (self.taskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.taskIdentifier];
self.taskIdentifier = UIBackgroundTaskInvalid;
}
} @end

下一步,WLButtonCountdownManager 拥有一个线程池(也叫并发操作队列,规定队列中最多只允许存在 20 个并发线程),每分配一个计时器(即创建一个子线程)就将其放入池子中,计时器跑完以后会自动从池子里销毁。

在创建计时任务之前,Manager 从池子里检索是否有相同 key 的计时任务,如果任务存在,直接回调计时操作。否则,新建一个标识为 key 的任务。

- (void)scheduledCountDownWithKey:(NSString *)aKey
timeInterval:(NSTimeInterval)timeInterval
countingDown:(void (^)(NSTimeInterval))countingDown
finished:(void (^)(NSTimeInterval))finished
{
if (timeInterval > 120) {
NSCAssert(NO, @"受操作系统后台时间限制,倒计时时间规定不得大于 120 秒.");
} if (_pool.operations.count >= 20) // 最多 20 个并发线程
return; WLCountdownTask *task = nil;
if ([self coundownTaskExistWithKey:aKey task:&task]) {
task.countingDownBlcok = countingDown;
task.finishedBlcok = finished;
if (countingDown) {
countingDown(task.leftTimeInterval);
}
} else {
task = [[WLCountdownTask alloc] init];
task.name = aKey;
task.leftTimeInterval = timeInterval;
task.countingDownBlcok = countingDown;
task.finishedBlcok = finished;
[_pool addOperation:task];
}
} - (BOOL)coundownTaskExistWithKey:(NSString *)akey
task:(NSOperation *__autoreleasing _Nullable *)task
{
__block BOOL taskExist = NO;
[_pool.operations enumerateObjectsUsingBlock:^(__kindof NSOperation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj.name isEqualToString:akey]) {
if (task) *task = obj;
taskExist = YES;
*stop = YES;
}
}]; return taskExist;
}

Last

至此,解决方案到此就结束了

iOS “获取验证码”按钮的倒计时功能的更多相关文章

  1. Angular.js 使用获取验证码按钮实现-倒计时

    获取验证码界面效果如图: 需要实现以下逻辑 按钮不可选 --输入电话号码,按钮可选 --点击获取,进入倒计时,按钮不可选 --倒计时结束,回到初识状态 核心代码: var cd = 60; var t ...

  2. [RN] React Native 获取验证码 按钮

    React Native 获取验证码 按钮 效果如图: 实现方法: 一.获取验证码 按钮组件 封装 CountDownButton.js "use strict"; import ...

  3. js实现发送短信验证码后的倒计时功能(无视页面刷新)

    [1].[代码] 这是页面上的发送验证码按钮 跳至 [1] [2] [3]<input id="second" type="button" value=& ...

  4. button获取验证码60秒倒计时 直接用

    __block ; __block UIButton *verifybutton = _GetverificationBtn; verifybutton.enabled = NO; dispatch_ ...

  5. 基于JS实现发送短信验证码后的倒计时功能(无视页面刷新,页面关闭不进行倒计时功能)

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. JS 获取验证码按钮改变案例

    HTML代码 <div class="box"> <label for="">手机号</label> <input t ...

  7. iOS点击获取短信验证码按钮

    概述 iOS点击获取短信验证码按钮, 由于 Demo整体测试运行效果 , 整个修改密码界面都已展现, 并附送正则表达式及修改密码逻辑. 详细 代码下载:http://www.demodashi.com ...

  8. 前端学习——ionic/AngularJs——获取验证码倒计时按钮

     按钮功能为:点击"获取验证码"--按钮不可用-设置倒计时-60秒后重新获取. 代码借鉴于:http://plnkr.co/edit/Swj82MpJSix3a47jZRHP?p= ...

  9. day80:luffy:短信sdk接入&点击获取验证码&注册功能的实现&Celery实现短信发送功能

    目录 1.短信sdk接入 2.前端点击获取验证码效果 3.注册后端接口实现 4.注册-前端 5.Celery 6.Celery完成短信发送功能 1.短信sdk接入 1.准备工作 1.下载云通讯相关的文 ...

随机推荐

  1. JAVA NIO 类库的异步通信框架netty和mina

    Netty 和 Mina 我究竟该选择哪个? 根据我的经验,无论选择哪个,都是个正确的选择.两者各有千秋,Netty 在内存管理方面更胜一筹,综合性能也更优.但是,API 变更的管理和兼容性做的不是太 ...

  2. 现代程序设计——homework-06

    题目: http://www.cnblogs.com/xinz/p/3382048.html 这是一道要多坑有多坑的题目,题目有很多步.综合起来就是这个意思: 在很久以前,有一个大牛,当然那个时候他还 ...

  3. Git 一些日常使用积累

    本来不想写这样的东西的,因为随处谷歌百度都有一大堆!但是,我却总是在百度谷歌,我在想,为什么我不自己写一篇存进来,顺便加深印象呢?既然这样,这篇随笔,就真的变成随笔好了,随时修改,随时添加. Git ...

  4. [iOS 多线程 & 网络 - 1.3] - NSOperation

    A.NSOperation的基本使用 1.NSOperation的作用 配合使用NSOperation和NSOperationQueue也能实现多线程编程 NSOperation和NSOperatio ...

  5. UVALIVE 4970 最小权匹配

    首先贴一下这道题的BNU地址,UVA地址自己找吧. http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=11852 题意:这道题的意思就是,给你N个棋子的 ...

  6. linux的shell脚本入门

    Linux shell脚本入门教程 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活 的工具.Shell不仅仅是命令的收集,而且是一门非常 ...

  7. Unity3D之移植学习笔记:移植到Android平台更好的方法

    接上文,之前我们采用了直接将Unity项目导出为Eclipse项目来修改的方式,这种做法存在的一个最大的问题就是:每当Unity被修改之后,都需要重新导出,而每次导出的项目在Android平台方面的J ...

  8. 调用DEDE日期时间格式整理大全

    dedecms 日期时间格式大全,大家可以根据需要选择.DEDECMS利用strftime()函数格式化时间的所有参数详解,包括年份日期进制.小时格式等,大家收藏吧,呵. 日期时间格式 (利用strf ...

  9. window.showModalDialog基础

    本文转载:http://www.cnblogs.com/sunnycoder/archive/2010/05/05/1728047.html 基本知识 l  showModalDialog() (IE ...

  10. codeforces Gym 100500C C. ICPC Giveaways 排序

    Problem C. ICPC GiveawaysTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/1005 ...