IU
目标
封装一个满足基本功能并且方便使用的倒计时按钮,关键是:
要优雅
需要实现的基本功能
以获取短信验证码为例。用户点击获取验证码按钮后,按钮enabled立即设置为NO,并且向后台发送请求,若请求失败,恢复按钮的enabled,反之开始倒计时,期间持续刷新按钮文本,倒计时结束后重置按钮。
需要处理的几个点
1.用户点击按钮
此时按钮的enabled立即变为NO,并向后台发起请求。
2.请求结束
若请求成功,开始倒计时,反之恢复按钮的可点状态并提示用户重试。
3.倒计时进行中
持续刷新按钮文本。
4.倒计时结束
恢复按钮的可点状态,重置按钮文本。
倒计时按钮封装
将上述几个需要处理的点以block的方式封装:
#import "CQCountDownButton.h" #import typedef void(^ButtonClickedBlock)(); typedef void(^CountDownStartBlock)(); typedef void(^CountDownUnderwayBlock)(NSInteger restCountDownNum); typedef void(^CountDownCompletionBlock)(); @interface CQCountDownButton () /** 控制倒计时的timer */ @property (nonatomic, strong) MSWeakTimer *timer; /** 按钮点击事件的回调 */ @property (nonatomic, copy) ButtonClickedBlock buttonClickedBlock; /** 倒计时开始时的回调 */ @property (nonatomic, copy) CountDownStartBlock countDownStartBlock; /** 倒计时进行中的回调(每秒一次) */ @property (nonatomic, copy) CountDownUnderwayBlock countDownUnderwayBlock; /** 倒计时完成时的回调 */ @property (nonatomic, copy) CountDownCompletionBlock countDownCompletionBlock; @end @implementation CQCountDownButton { /** 倒计时开始值 */ NSInteger _startCountDownNum; /** 剩余倒计时的值 */ NSInteger _restCountDownNum; } /** 构造方法 @param frame frame @param duration 倒计时时间 @param buttonClicked 按钮点击事件的回调 @param countDownStart 倒计时开始时的回调 @param countDownUnderway 倒计时进行中的回调(每秒一次) @param countDownCompletion 倒计时完成时的回调 @return 倒计时button */ - (instancetype)initWithFrame:(CGRect)frame duration:(NSInteger)duration buttonClicked:(void(^)())buttonClicked countDownStart:(void(^)())countDownStart countDownUnderway:(void(^)(NSInteger restCountDownNum))countDownUnderway countDownCompletion:(void(^)())countDownCompletion { if (self = [super initWithFrame:frame]) { _startCountDownNum = duration; self.buttonClickedBlock = buttonClicked; self.countDownStartBlock = countDownStart; self.countDownUnderwayBlock = countDownUnderway; self.countDownCompletionBlock = countDownCompletion; [self addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside]; } return self; } /** 按钮点击 */ - (void)buttonClicked:(CQCountDownButton *)sender { sender.enabled = NO; self.buttonClickedBlock(); } /** 开始倒计时 */ - (void)startCountDown { if (self.timer) { [self.timer invalidate]; self.timer = nil; } _restCountDownNum = _startCountDownNum; self.countDownStartBlock(); // 调用倒计时开始的block self.timer = [MSWeakTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(refreshButton) userInfo:nil repeats:YES dispatchQueue:dispatch_get_main_queue()]; } /** 刷新按钮内容 */ - (void)refreshButton { _restCountDownNum --; self.countDownUnderwayBlock(_restCountDownNum); // 调用倒计时进行中的回调 if (_restCountDownNum == 0) { [self.timer invalidate]; self.timer = nil; _restCountDownNum = _startCountDownNum; self.countDownCompletionBlock(); // 调用倒计时完成的回调 self.enabled = YES; } }
畅快使用,一个方法搞定所有事件处理及回调
__weak __typeof__(self) weakSelf = self; self.countDownButton = [[CQCountDownButton alloc] initWithFrame:CGRectMake(90, 90, 150, 30) duration:10 buttonClicked:^{ //------- 按钮点击 -------// [SVProgressHUD showWithStatus:@"正在获取验证码..."]; // 请求数据 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ int a = arc4random() % 2; if (a == 0) { // 获取成功 [SVProgressHUD showSuccessWithStatus:@"验证码已发送"]; // 获取到验证码后开始倒计时 [weakSelf.countDownButton startCountDown]; } else { // 获取失败 [SVProgressHUD showErrorWithStatus:@"获取失败,请重试"]; weakSelf.countDownButton.enabled = YES; } }); } countDownStart:^{ //------- 倒计时开始 -------// NSLog(@"倒计时开始"); } countDownUnderway:^(NSInteger restCountDownNum) { //------- 倒计时进行中 -------// [weakSelf.countDownButton setTitle:[NSString stringWithFormat:@"再次获取(%ld秒)", restCountDownNum] forState:UIControlStateNormal]; } countDownCompletion:^{ //------- 倒计时结束 -------// [weakSelf.countDownButton setTitle:@"点击获取验证码" forState:UIControlStateNormal]; NSLog(@"倒计时结束"); }];
亮点
倒计时进行中的block,通过传递剩余倒计时数值,优雅实现按钮的持续更新:
countDownUnderway:^(NSInteger restCountDownNum) { //------- 倒计时进行中 -------// [weakSelf.countDownButton setTitle:[NSString stringWithFormat:@"再次获取(%ld秒)", restCountDownNum] forState:UIControlStateNormal]; }
Block or Delegate?
每当涉及到回调的时候我都会考虑这个问题。
block强调结果而delegate强调过程,在这里我们显然要的是结果。还有就是,如果这里采用delegate,代码的组织将更繁琐(需要4个代理方法依次对应4个block)。
但是,如果真的用delegate的话,用#pragma mark - count down将4个代理方法放在一起,想较block而言代码结构会更加的层次分明,这也是大家通常认为delegate更容易维护的原因之一吧。
使用block还是delegate这是一个仁者见仁智者见智的问题,我个人认为如果你对你的代码有较高要求、懂得换位思考,那么你不管使用delegate还是block都可以写出赏心悦目的代码,反之,都将惨不忍睹。
内存管理
因为涉及到timer和block,所以内存这一块要警惕。
关于timer
MSWeakTimer 你值得拥有
Thread-safe NSTimer drop-in alternative that doesn't retain the target and supports being used with GCD queues.
关于block
注意使用weakSelf。
最重要的还是用instrument彻底的检查一下。
我已经用instrument检查多次了,请放心使用。
给dalao递优秀三方库.gif
demo
期望
希望有大佬打赏一块钱买鸡蛋。
flow down the poor's tears
作者:无夜之星辰
链接:http://www.jianshu.com/p/34e87194fb83
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。