学习计划 (2) - 自定义验证码按钮

今天要做的是一个倒计时按钮,也是项目当中经常遇到的一类需求。
最终实现效果如下:


验证码按钮.png

而实现这种验证码,方式很多,有人用NSTimer进行开发的,也有用dispatch_source_t 进行开发的。
但是NSTimer在运用过程中需要注意的地方很多,如果使用不当就会造成内存无法释放,导致内存溢出的可能。
所以,我后来又改用dispatch_source_t 完成了这一需求。
至于dispatch_source_t 的具体详解,有需要的可以去查看相关资料,毕竟今天的主题是利用它其中的一项功能dispatch_source_set_timer。

资料传送门:
https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html

https://www.tuicool.com/articles/nIRJf2e
http://www.dreamingwish.com/article/grand-central-dispatch-basic-3.html

下面开始我们的正题:

由于该项目需要用到上一篇中的文本输入框,所以直接在上一篇的项目中进行使用了。
同样是选用Model作为参数的模式
其实关于模式的选用,就是根据自己的工作环境来决定的。
我选用这种模式的原因就是,很长时间没看的代码,需要时间成本去了解.
而这种模式的可读性较强,而且将属性设置和构建分离,方便以后的维护。

model的数据也就是常见的属性和一些需要自定义属性设置,当然,以后扩展还会很多,今天的按钮只是其中之一,所以目前属性就这些吧.

@interface GWButtonModel : NSObject
//标题
@property (nonatomic,readwrite,copy)NSString *title;
//标题颜色
@property (nonatomic,readwrite,strong)UIColor *titleColor;
//边框颜色
@property (nonatomic,readwrite,strong)UIColor *borderColor;
//边框宽度
@property (nonatomic,readwrite,unsafe_unretained)CGFloat borderWidth;
//刷新计时
@property (nonatomic,readwrite,unsafe_unretained)NSTimeInterval refreshTime;
//字体
@property (nonatomic,readwrite,strong)UIFont *font;
//背景色
@property (nonatomic,readwrite,strong)UIColor *backgroundColor;
//圆角
@property (nonatomic,readwrite,unsafe_unretained)CGFloat cornerRadius;
@end

在构建GWButton类之前,我们应该去分析我们的实际应用场景,所以我先来说说我在项目中用的场景:

1. 点击按钮 
2. 启用代理到控制器中判断用户是否存在(因为是可变的需求,所以不适合放在GWButton类中)
3. 如果用户存在,那么执行获取验证码的网络请求
(我将获取验证码的网络请求放在GWButton当中,因为这是一个公用方法,无论其他需求怎么变,验证码你得获取吧。也减小了控制器中代码的冗余)
4. 获取到验证码之后开始倒计时。否则就是保持原样
5. 倒计时完毕恢复原来的样式

分析出了完整的需求之后,事情就简单多了

  1. 构建GWButton类
@protocol GWSecurityCodeDelegate <NSObject>

/**
 发送验证码
 */
- (void)sendSecurityCode;
@end

@interface GWButton : UIButton

@property (nonatomic,readwrite,weak)id<GWSecurityCodeDelegate>   securityCodeDelegate;

/**
 验证码

 @param frame 布局
 @param buttonModel 按钮数据模型

 */
+ (instancetype)securityCodeButtonWithFrame:(CGRect)frame
                                buttonModel:(GWButtonModel *)buttonModel;

/**
 发送获取验证码请求

 @param type 验证码类型
 @param tel 手机号码
 @param intervalTime 倒计时时间
 */
- (void)securityCodeType:(NSString *)type
           userTelephone:(NSString *)tel
              refresTime:(NSTimeInterval)intervalTime;
@end
这里的两个说明:
1. 为什么要用securityCodeDelegate命名,不用delegate.因为验证码只是这个按钮的其中一种。
以后还会添加别的按钮类型,它们的代理可能是别的类型。这样命名更加容易识别。就像WKWebview中的两个代理一样。

2. 因为获取验证码的请求,根据不同后台人员,写的可能不同,所以我只能列出个样例,这里需要的是验证码类型和手机号码两个参数。

接下来我们就看一下,我们的核心代码了。
其实关于为什么选用dispatch_source,我们可以看一下,官方的介绍:

A dispatch source is a fundamental data type that coordinates the processing of specific low-level system events. 
Grand Central Dispatch supports the following types of dispatch sources:
Timer dispatch sources generate periodic notifications.

好吧。有很多条,我就不展示了,上面有网址自己看了。
那么第一条就是定时器资源的使用。我们都知道dispatch系列存在都是不用我们自主管理线程和内存释放的。这个就要比NSTimer有优势多了。

而且下面这段话也说明了的Dispatch Sources方便之处:

Unlike tasks that you submit to a queue manually, dispatch sources provide a continuous source of events for your application. 
A dispatch source remains attached to its dispatch queue until you cancel it explicitly.
 While attached, it submits its associated task code to the dispatch queue whenever the corresponding event occurs. 
Some events, such as timer events, occur at regular intervals but most occur only sporadically as specific conditions arise. 
For this reason, dispatch sources retain their associated dispatch queue to prevent it from being released prematurely while events may still be pending.

简单来说就是:

Unlike tasks that you submit to a queue manually, dispatch sources provide a continuous source of events for your application. 
与您手动提交到队列的任务不同,分派源为您的应用程序提供了一个连续的事件源(有道翻译,你值得拥有)

我们只需要设定好参数,结束和需要改变的事件就可以了,其他的它会帮我们做好。

下面来看实际的代码:

 __block int timeout = intervalTime; //倒计时时间

//创建dispatch_source源
1. dispatch_source_t   _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
                                       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
//间隔时间
uint64_t  interval = 1.0*NSEC_PER_SEC;
//获取开始的时间
dispatch_time_t  start = dispatch_walltime(NULL, 0);
//设置执行的时间
2. dispatch_source_set_timer(_timer, start, interval, 0);

//设置资源回调.
3.dispatch_source_set_event_handler(_timer, ^{
       if(timeout<=0){//倒计时结束,关闭的事件
  
       }else{  //计时中的事件
      
            timeout--;
        }
   });

//启动_timer
4. dispatch_resume(_timer);

上面代码中的NSEC_PER_SEC嘛意思?

#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull

NSEC:纳秒。
USEC:微秒。
SEC:秒
PER:每
1   NSEC_PER_SEC,每秒有多少纳秒。
2   USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
3   NSEC_PER_USEC,每毫秒有多少纳秒。

最后就是简单的执行顺序:
执行按钮事件:

#pragma mark 验证码刷新事件
- (void)securityCodeButtonAction:(GWButton *)button{
    
  //这里写的目的是为了防止用户频繁点击按钮造成多次请求的情况
  //网上一些其他的方法例如 分类什么的都试过了还是这个好用。
    button.enabled = NO;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        button.enabled = YES;
    });
    
    if([button.securityCodeDelegate respondsToSelector:@selector(sendSecurityCode)]){
        [button.securityCodeDelegate sendSecurityCode];
    }
}

然后执行代理:

- (void)securityCodeType:(NSString *)type
           userTelephone:(NSString *)tel
              refresTime:(NSTimeInterval)intervalTime{
    //可以将网络请求的方法放在这里,请求成功执行开始计时的方法。
    //还有这个请求只是放在这里给大家看个样例,demo中是没有的。
    kWeakSelf
    [GWVerificationCodeManager GWGetVerifyNUmberRequstWithPhone:[PMBTools convertNullOrNil:tel]
                                                             type:[PMBTools convertNullOrNil:type]
              success:^(id responseobject) {
                  NSDictionary *dict = (NSDictionary *)responseobject;
                  NSLog(@"验证码请求结果:%@",dict);
                  if([dict[@"resCode"] isEqualToString:@"020000"]){
                      [SVProgressHUD showSuccessWithStatus:dict[@"resMsg"]];
                      [weakself beginToCountDownWithRefresTime:intervalTime];
                  }else{
                      [SVProgressHUD showSuccessWithStatus:dict[@"resMsg"]];
                  }
              } failure:^(NSError *error) {
                  
              } progress:^(float progress) {
                  
              }];
}

最后就是执行倒计时了

- (void)beginToCountDownWithRefresTime:(NSTimeInterval)intervalTime{
    __block int timeout = intervalTime; //倒计时时间
    kWeakSelf
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
    
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
    
    dispatch_source_set_event_handler(_timer, ^{
        if(timeout<=0){//倒计时结束,关闭
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakself setUserInteractionEnabled:YES];
                [weakself setTitle:@"获取验证码" forState:UIControlStateNormal];
                [weakself setBackgroundImage:[PMBTools convertUIColorToUIImage:[PMBTools colorWithHexString:@"#FF9129"]] forState:UIControlStateNormal];
            });
        }else{
            int seconds = (timeout==60?timeout-1:timeout) % 60;
            dispatch_async(dispatch_get_main_queue(), ^{
                //设置界面的按钮显示 根据自己需求设置
                [weakself setBackgroundImage:[PMBTools convertUIColorToUIImage:getColor(207, 204, 207, 1)] forState:UIControlStateNormal];
                [weakself setTitle:[NSString stringWithFormat:@"重新获取(%ds)",seconds]
                          forState:UIControlStateNormal];
                [weakself setUserInteractionEnabled:NO];
            });
            timeout--;
        }
    });
    
    dispatch_resume(_timer);
}

demo地址:
https://github.com/yanggenwei/GWTextField


嗯,看到这里说明你认真看了,我会冥冥中开心一下下的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,363评论 25 707
  • 关于Dispatch Source Dispatch Source是GCD中的一种基本数据类型,从字面意思可称其为...
    杭研融合通信iOS阅读 3,513评论 1 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 名句:“一日之计在于晨,一晨之计在于起。” 时间管理的第一习惯是早起~ 早起是治家之本~曾国藩 叫醒我们的不是闹钟...
    洁子乌托邦阅读 249评论 0 0
  • 证件(深户适用) 大陆居民往来台湾通行证 入台证 我在深圳,从内地来此求学,户口形式是之前在内地(安徽),后随学习...
    tltangliang阅读 398评论 2 1