iOS検証コードのカウントダウンを実現し、終了してからカウントダウンを継続する


需要


Appには認証コードを送信するページがたくさんありますが、認証コードにかかわる場所にはカウントダウン機能があるに違いありません.製品は検証コードの送信を要求した後、カウントダウンが終了するまで検証コードを繰り返し送信しない.

最初のステップ


まずカウントダウン機能を実現し、ログイン画面を例にとると、ユーザーが携帯電話番号を入力した後、ボタンをクリックして検証コードを送信する必要があり、検証コードを送信することに成功した後、以下の方法を呼び出し、ボタンカウントダウン機能を実現する.
- (void)timerCountDownWithType:(BOUCountDownType)countDownType {
    
    _countDonwnType = countDownType;
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
    
    NSTimeInterval seconds = kMaxCountDownTime;
    NSDate *endTime = [NSDate dateWithTimeIntervalSinceNow:seconds];
    dispatch_source_set_event_handler(_timer, ^{
    
        int interval = [endTime timeIntervalSinceNow];
        if (interval <= 0) {
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
            	// , , 
            	[self.meeageButton setTitle:@" " forState:UIControlStateNormal];
                [self.meeageButton setEnabled:YES];
            });
        }
        else {
            dispatch_async(dispatch_get_main_queue(), ^{
                //  interval , 
                [self.meeageButton setTitle:@(interval).stringValue forState:UIControlStateNormal];
                [self.meeageButton setEnabled:NO];
            });
        }
    });
    dispatch_resume(_timer);
}

上記の方法ではボタンのカウントダウン機能が実現されていますが、戻るボタンをクリックせずに現在のページを離れるとカウントダウンは正常ですが、 , , です.ユーザーは認証コードを再送信できますが、前回のカウントダウン時間はまだ来ていません.この なので、上記のスキームは調整する必要があります.

ステップ2


考えた結果,カウントダウン機能を1つのクラスに個別にカプセル化し,重複コードの頻繁な書き込みを避けることにした.
1.BOUTimerManagerクラスを新設し、NSObjectから継承する
複数のページでカウントダウン機能が必要なため、カウントダウンが属するページを区別するには、次の列挙タイプを定義します.
typedef NS_ENUM(NSInteger, BOUCountDownType) {
    BOUCountDownTypeLogin,// 
    BOUCountDownTypeFindPassword,// 
    BOUCountDownTypeRegister,// 
    BOUCountDownTypeModifyPhone,// 
};
BOUTimerManager.hファイルで次の方法を定義します.
+ (instancetype)shareInstance;// 

- (void)timerCountDownWithType:(BOUCountDownType)countDownType;// , type 。

- (void)cancelTimerWithType:(BOUCountDownType)countDownType;// , type 。

カウントダウンプロセスでは、応答インタフェースはカウントダウン中またはカウントダウン完了処理に関するページロジックに基づいている必要があります.ここでは通知を送信する方法を使用して、カウントダウンプロセスとカウントダウン完了時に通知を送信します.ページ登録通知後、カウントダウン状態を受信することができます.BOUTimerManager.hでは、以下の内容を定義する必要があります.
#define kLoginCountDownCompletedNotification            @"kLoginCountDownCompletedNotification"
#define kFindPasswordCountDownCompletedNotification     @"kFindPasswordCountDownCompletedNotification"
#define kRegisterCountDownCompletedNotification            @"kRegisterCountDownCompletedNotification"
#define kModifyPhoneCountDownCompletedNotification            @"kModifyPhoneCountDownCompletedNotification"

#define kLoginCountDownExecutingNotification            @"kLoginCountDownExecutingNotification"
#define kFindPasswordCountDownExecutingNotification     @"kFindPasswordCountDownExecutingNotification"
#define kRegisterCountDownExecutingNotification            @"kRegisterCountDownExecutingNotification"
#define kModifyPhoneCountDownExecutingNotification            @"kModifyPhoneCountDownExecutingNotification"
BOUTimerManager.hのすべての内容は以下の通りです.
#import 

#define kLoginCountDownCompletedNotification            @"kLoginCountDownCompletedNotification"
#define kFindPasswordCountDownCompletedNotification     @"kFindPasswordCountDownCompletedNotification"
#define kRegisterCountDownCompletedNotification            @"kRegisterCountDownCompletedNotification"
#define kModifyPhoneCountDownCompletedNotification            @"kModifyPhoneCountDownCompletedNotification"

#define kLoginCountDownExecutingNotification            @"kLoginCountDownExecutingNotification"
#define kFindPasswordCountDownExecutingNotification     @"kFindPasswordCountDownExecutingNotification"
#define kRegisterCountDownExecutingNotification            @"kRegisterCountDownExecutingNotification"
#define kModifyPhoneCountDownExecutingNotification            @"kModifyPhoneCountDownExecutingNotification"

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, BOUCountDownType) {
    BOUCountDownTypeLogin,
    BOUCountDownTypeFindPassword,
    BOUCountDownTypeRegister,
    BOUCountDownTypeModifyPhone,
};


@interface BOUTimerManager : NSObject

DEF_SINGLETON(BOUTimerManager);

- (void)timerCountDownWithType:(BOUCountDownType)countDownType;

- (void)cancelTimerWithType:(BOUCountDownType)countDownType;

@end

NS_ASSUME_NONNULL_END

BOUTimerManager.mの実装内容は以下の通りである.
#import "BOUTimerManager.h"

#define kMaxCountDownTime           60// , 

@interface BOUTimerManager ()

@property (nonatomic, assign) BOUCountDownType countDonwnType;

@property (nonatomic, nullable, strong) dispatch_source_t loginTimer;// timer

@property (nonatomic, nullable, strong) dispatch_source_t findPwdTimer;// timer

@property (nonatomic, nullable, strong) dispatch_source_t registerTimer;// timer

@property (nonatomic, nullable, strong) dispatch_source_t modifyPhoneTimer;// timer

@end

@implementation BOUTimerManager

IMP_SINGLETON(BOUTimerManager);

- (void)timerCountDownWithType:(BOUCountDownType)countDownType {
    
    _countDonwnType = countDownType;
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
    
    NSTimeInterval seconds = kMaxCountDownTime;
    NSDate *endTime = [NSDate dateWithTimeIntervalSinceNow:seconds];
    dispatch_source_set_event_handler(_timer, ^{
    
        int interval = [endTime timeIntervalSinceNow];
        if (interval <= 0) {
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if ([_timer isEqual:self.loginTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCountDownCompletedNotification object:@(interval)];
                } else if ([_timer isEqual:self.findPwdTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kFindPasswordCountDownCompletedNotification object:@(interval)];
                } else if ([_timer isEqual:self.registerTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kRegisterCountDownCompletedNotification object:@(interval)];
                } else if ([_timer isEqual:self.modifyPhoneTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kModifyPhoneCountDownCompletedNotification object:@(interval)];
                }
            
            });
        }
        else {
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if ([_timer isEqual:self.loginTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCountDownExecutingNotification object:@(interval)];
                } else if ([_timer isEqual:self.findPwdTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kFindPasswordCountDownExecutingNotification object:@(interval)];
                } else if ([_timer isEqual:self.registerTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kRegisterCountDownExecutingNotification object:@(interval)];
                } else if ([_timer isEqual:self.modifyPhoneTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kModifyPhoneCountDownExecutingNotification object:@(interval)];
                }
                
            });
        }
    });
    
    if (self.countDonwnType == BOUCountDownTypeLogin) {
        self.loginTimer = _timer;
    } else if (self.countDonwnType == BOUCountDownTypeFindPassword) {
        self.findPwdTimer = _timer;
    } else if (self.countDonwnType == BOUCountDownTypeRegister) {
        self.registerTimer = _timer;
    } else if (self.countDonwnType == BOUCountDownTypeModifyPhone) {
        self.modifyPhoneTimer = _timer;
    }
    
    dispatch_resume(_timer);
}

- (void)cancelTimerWithType:(BOUCountDownType)countDownType {
    switch (countDownType) {
        case BOUCountDownTypeLogin:
            if (self.loginTimer) {
                dispatch_source_cancel(self.loginTimer);
            }
            
            break;
        case BOUCountDownTypeRegister:
            if (self.registerTimer) {
                dispatch_source_cancel(self.registerTimer);
            }
            
            break;
        case BOUCountDownTypeModifyPhone:
            if (self.modifyPhoneTimer) {
                dispatch_source_cancel(self.modifyPhoneTimer);
            }
            
            break;
        case BOUCountDownTypeFindPassword:
            if (self.findPwdTimer) {
                dispatch_source_cancel(self.findPwdTimer);
            }
            
            break;
        default:
            break;
    }
}

@end

DEF_SINGLETONは、一例宣言のマクロ定義であり、IMP_SINGLETONは、一例で実現されるマクロ定義である
#undef    DEF_SINGLETON
#define DEF_SINGLETON( __class ) \
+ (__class *)sharedInstance;

#undef    IMP_SINGLETON
#define IMP_SINGLETON( __class ) \
+ (__class *)sharedInstance \
{ \
static dispatch_once_t once; \
static __class * __singleton__; \
dispatch_once( &once, ^{ __singleton__ = [[__class alloc] init]; } ); \
return __singleton__; \
}

2.コントローラでの処理ロジック、ログイン画面を例に

initメソッドにカウントダウン通知イベントを登録
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginTimerCountDownCompleted) name:kLoginCountDownCompletedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginTimerCountDownExecutingWithTimeOut:) name:kLoginCountDownExecutingNotification object:nil];


#pragma mark - NSNotification  

- (void)loginTimerCountDownExecutingWithTimeOut:(NSNotification *)notification {
    NSInteger timeOut = [notification.object integerValue];
    NSString *timeStr = [NSString stringWithFormat:@"(%.2ld) ",(long)timeOut];
    self.topView.btnCountDown.selected = YES;//  self.topView.btnCountDown button
    [self.topView.btnCountDown setTitle:timeStr forState:UIControlStateNormal];//  self.topView.btnCountDown button
    [self.topView.btnCountDown setTitleColor:COLOR_WITH_HEX(0x999999) //  self.topView.btnCountDown buttonforState:UIControlStateNormal];
    self.topView.btnCountDown.userInteractionEnabled = NO;
}

- (void)loginTimerCountDownCompleted {
    self.topView.btnCountDown.selected = NO;//  self.topView.btnCountDown button
    [self.topView.btnCountDown setTitle:@" " forState:UIControlStateNormal];//  self.topView.btnCountDown button
    [self.topView.btnCountDown setTitleColor:NavigationColor forState:UIControlStateNormal];//  self.topView.btnCountDown button
    self.topView.btnCountDown.userInteractionEnabled = YES;//  self.topView.btnCountDown button
}
deallocメソッドでの登録破棄通知
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

携帯電話番号を入力したら、送信認証コードをクリックし、バックグラウンドインタフェースが成功した後、カウントダウンを開始します.
- (void)sendSMSRequestWithPhone:(NSString *)phoneNum sender:(UIButton *)sender {
	// 
	//...
	// 
	[[BOUTimerManager sharedInstance] timerCountDownWithType:BOUCountDownTypeLogin];
}

ログインボタンをクリックし、バックグラウンドインタフェースが戻ってきたら、ログインインタフェース検証コードカウントダウンをキャンセルします.
- (void)clickLoginAction:(UIButton *)sender {
	// 
	//...
	// 
	[[BOUTimerManager sharedInstance] cancelTimerWithType:BOUCountDownTypeLogin];
}

の最後の部分


以上が実現プロセス全体です