今天要做的是一個(gè)倒計(jì)時(shí)按鈕,也是項(xiàng)目當(dāng)中經(jīng)常遇到的一類需求认境。
最終實(shí)現(xiàn)效果如下:
而實(shí)現(xiàn)這種驗(yàn)證碼,方式很多,有人用NSTimer進(jìn)行開發(fā)的垃喊,也有用dispatch_source_t 進(jìn)行開發(fā)的笑撞。
但是NSTimer在運(yùn)用過程中需要注意的地方很多默终,如果使用不當(dāng)就會(huì)造成內(nèi)存無法釋放,導(dǎo)致內(nèi)存溢出的可能兼蜈。
所以历等,我后來又改用dispatch_source_t 完成了這一需求妇智。
至于dispatch_source_t 的具體詳解滥玷,有需要的可以去查看相關(guān)資料,畢竟今天的主題是利用它其中的一項(xiàng)功能dispatch_source_set_timer巍棱。
https://www.tuicool.com/articles/nIRJf2e
http://www.dreamingwish.com/article/grand-central-dispatch-basic-3.html
下面開始我們的正題:
由于該項(xiàng)目需要用到上一篇中的文本輸入框,所以直接在上一篇的項(xiàng)目中進(jìn)行使用了。
同樣是選用Model作為參數(shù)的模式
其實(shí)關(guān)于模式的選用,就是根據(jù)自己的工作環(huán)境來決定的航徙。
我選用這種模式的原因就是,很長時(shí)間沒看的代碼杠袱,需要時(shí)間成本去了解.
而這種模式的可讀性較強(qiáng),而且將屬性設(shè)置和構(gòu)建分離凿掂,方便以后的維護(hù)纹蝴。
model的數(shù)據(jù)也就是常見的屬性和一些需要自定義屬性設(shè)置,當(dāng)然糠涛,以后擴(kuò)展還會(huì)很多耙旦,今天的按鈕只是其中之一,所以目前屬性就這些吧.
@interface GWButtonModel : NSObject
//標(biāo)題
@property (nonatomic,readwrite,copy)NSString *title;
//標(biāo)題顏色
@property (nonatomic,readwrite,strong)UIColor *titleColor;
//邊框顏色
@property (nonatomic,readwrite,strong)UIColor *borderColor;
//邊框?qū)挾?@property (nonatomic,readwrite,unsafe_unretained)CGFloat borderWidth;
//刷新計(jì)時(shí)
@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
在構(gòu)建GWButton類之前锉罐,我們應(yīng)該去分析我們的實(shí)際應(yīng)用場(chǎng)景,所以我先來說說我在項(xiàng)目中用的場(chǎng)景:
1. 點(diǎn)擊按鈕
2. 啟用代理到控制器中判斷用戶是否存在(因?yàn)槭强勺兊男枨笕颇铮圆贿m合放在GWButton類中)
3. 如果用戶存在,那么執(zhí)行獲取驗(yàn)證碼的網(wǎng)絡(luò)請(qǐng)求
(我將獲取驗(yàn)證碼的網(wǎng)絡(luò)請(qǐng)求放在GWButton當(dāng)中,因?yàn)檫@是一個(gè)公用方法险领,無論其他需求怎么變,驗(yàn)證碼你得獲取吧绢陌。也減小了控制器中代碼的冗余)
4. 獲取到驗(yàn)證碼之后開始倒計(jì)時(shí)。否則就是保持原樣
5. 倒計(jì)時(shí)完畢恢復(fù)原來的樣式
分析出了完整的需求之后臭笆,事情就簡單多了
- 構(gòu)建GWButton類
@protocol GWSecurityCodeDelegate <NSObject>
/**
發(fā)送驗(yàn)證碼
*/
- (void)sendSecurityCode;
@end
@interface GWButton : UIButton
@property (nonatomic,readwrite,weak)id<GWSecurityCodeDelegate> securityCodeDelegate;
/**
驗(yàn)證碼
@param frame 布局
@param buttonModel 按鈕數(shù)據(jù)模型
*/
+ (instancetype)securityCodeButtonWithFrame:(CGRect)frame
buttonModel:(GWButtonModel *)buttonModel;
/**
發(fā)送獲取驗(yàn)證碼請(qǐng)求
@param type 驗(yàn)證碼類型
@param tel 手機(jī)號(hào)碼
@param intervalTime 倒計(jì)時(shí)時(shí)間
*/
- (void)securityCodeType:(NSString *)type
userTelephone:(NSString *)tel
refresTime:(NSTimeInterval)intervalTime;
@end
這里的兩個(gè)說明:
1. 為什么要用securityCodeDelegate命名,不用delegate.因?yàn)轵?yàn)證碼只是這個(gè)按鈕的其中一種愁铺。
以后還會(huì)添加別的按鈕類型闻鉴,它們的代理可能是別的類型。這樣命名更加容易識(shí)別孟岛。就像WKWebview中的兩個(gè)代理一樣。
2. 因?yàn)楂@取驗(yàn)證碼的請(qǐng)求在验,根據(jù)不同后臺(tái)人員,寫的可能不同盏触,所以我只能列出個(gè)樣例块饺,這里需要的是驗(yàn)證碼類型和手機(jī)號(hào)碼兩個(gè)參數(shù)。
接下來我們就看一下辨嗽,我們的核心代碼了淮腾。
其實(shí)關(guān)于為什么選用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.
好吧圆凰。有很多條,我就不展示了挑童,上面有網(wǎng)址自己看了跃须。
那么第一條就是定時(shí)器資源的使用。我們都知道dispatch系列存在都是不用我們自主管理線程和內(nèi)存釋放的尽楔。這個(gè)就要比NSTimer有優(yōu)勢(shì)多了玉雾。
而且下面這段話也說明了的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.
與您手動(dòng)提交到隊(duì)列的任務(wù)不同复旬,分派源為您的應(yīng)用程序提供了一個(gè)連續(xù)的事件源(有道翻譯冲泥,你值得擁有)
我們只需要設(shè)定好參數(shù)壁涎,結(jié)束和需要改變的事件就可以了志秃,其他的它會(huì)幫我們做好。
下面來看實(shí)際的代碼:
__block int timeout = intervalTime; //倒計(jì)時(shí)時(shí)間
//創(chuàng)建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));
//間隔時(shí)間
uint64_t interval = 1.0*NSEC_PER_SEC;
//獲取開始的時(shí)間
dispatch_time_t start = dispatch_walltime(NULL, 0);
//設(shè)置執(zhí)行的時(shí)間
2. dispatch_source_set_timer(_timer, start, interval, 0);
//設(shè)置資源回調(diào).
3.dispatch_source_set_event_handler(_timer, ^{
if(timeout<=0){//倒計(jì)時(shí)結(jié)束,關(guān)閉的事件
}else{ //計(jì)時(shí)中的事件
timeout--;
}
});
//啟動(dòng)_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率碾,每秒有多少毫秒屋彪。(注意是指在納秒的基礎(chǔ)上)
3 NSEC_PER_USEC,每毫秒有多少納秒歧匈。
最后就是簡單的執(zhí)行順序:
執(zhí)行按鈕事件:
#pragma mark 驗(yàn)證碼刷新事件
- (void)securityCodeButtonAction:(GWButton *)button{
//這里寫的目的是為了防止用戶頻繁點(diǎn)擊按鈕造成多次請(qǐng)求的情況
//網(wǎng)上一些其他的方法例如 分類什么的都試過了還是這個(gè)好用砰嘁。
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];
}
}
然后執(zhí)行代理:
- (void)securityCodeType:(NSString *)type
userTelephone:(NSString *)tel
refresTime:(NSTimeInterval)intervalTime{
//可以將網(wǎng)絡(luò)請(qǐng)求的方法放在這里,請(qǐng)求成功執(zhí)行開始計(jì)時(shí)的方法矮湘。
//還有這個(gè)請(qǐng)求只是放在這里給大家看個(gè)樣例,demo中是沒有的缅阳。
kWeakSelf
[GWVerificationCodeManager GWGetVerifyNUmberRequstWithPhone:[PMBTools convertNullOrNil:tel]
type:[PMBTools convertNullOrNil:type]
success:^(id responseobject) {
NSDictionary *dict = (NSDictionary *)responseobject;
NSLog(@"驗(yàn)證碼請(qǐng)求結(jié)果:%@",dict);
if([dict[@"resCode"] isEqualToString:@"020000"]){
[SVProgressHUD showSuccessWithStatus:dict[@"resMsg"]];
[weakself beginToCountDownWithRefresTime:intervalTime];
}else{
[SVProgressHUD showSuccessWithStatus:dict[@"resMsg"]];
}
} failure:^(NSError *error) {
} progress:^(float progress) {
}];
}
最后就是執(zhí)行倒計(jì)時(shí)了
- (void)beginToCountDownWithRefresTime:(NSTimeInterval)intervalTime{
__block int timeout = intervalTime; //倒計(jì)時(shí)時(shí)間
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){//倒計(jì)時(shí)結(jié)束,關(guān)閉
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
[weakself setUserInteractionEnabled:YES];
[weakself setTitle:@"獲取驗(yàn)證碼" 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(), ^{
//設(shè)置界面的按鈕顯示 根據(jù)自己需求設(shè)置
[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
嗯秀撇,看到這里說明你認(rèn)真看了呵燕,我會(huì)冥冥中開心一下下的件相。