在后台执行短信验证码倒计时

前言

我们一般做的短信验证码功能,应用在前台运行的时候,肯定是没有问题的,但是点击一下Home键进入后台挂起的状态,我们的定时器就停止了。

网上很多说记录什么进入后台的时间,我觉的那太费事了,我们完全可以通过申请应用在后台执行来执行我们的倒计时。

1、先放我们倒计时的代码,这段代码在模拟器运行是没有问题的,但是在真机进入挂起状态会停止

<pre>

  • (void)sentPhoneCodeTimeMethod {
    //倒计时时间 - 60S
    __block NSInteger timeOut = 59;
    //执行队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //计时器 -》 dispatch_source_set_timer自动生成
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    dispatch_source_set_event_handler(timer, ^{
    if (timeOut <= 0) {
    dispatch_source_cancel(timer);
    //主线程设置按钮样式
    dispatch_async(dispatch_get_main_queue(), ^{
    // 倒计时结束
    [self.captchaButton setBackgroundImage:kImage(kCaptchaButtonNormal) forState:UIControlStateNormal];
    [self.captchaButton setTitle:kCaptchaButtonTitle forState:UIControlStateNormal];
    [self.captchaButton setEnabled:YES];

              [self.captchaButton setUserInteractionEnabled:YES];
              
          });
      } else {
          //开始计时
          //剩余秒数 seconds
          NSInteger seconds = timeOut % 60;
          NSString *strTime = [NSString stringWithFormat:@"%.1ld", seconds];
          //主线程设置按钮样式
          dispatch_async(dispatch_get_main_queue(), ^{
              [UIView beginAnimations:nil context:nil];
              [UIView setAnimationDuration:1.0];
              
              [self.captchaButton setBackgroundImage:kImage(kCaptchaButtonSelected) forState:UIControlStateNormal];
              NSString *title = [NSString stringWithFormat:@"%@ s",strTime];
              [self.captchaButton setTitle:title forState:UIControlStateNormal];
              
              [UIView commitAnimations];
              //计时器件不允许点击
              [self.captchaButton setUserInteractionEnabled:NO];
              
          });
          timeOut--;
          
      }
    

    });
    dispatch_resume(timer);
    }
    </pre>

2、下面是我们用到的一个小技巧

一般来说,如果不进行后台申请,在iOS系统上,当应用退到后台后,只有5s的时间去执行代码,之后将进入挂起状态。只有像音频播放、定位、newsstand、VoIP等功能才能持续在后台运行。但是开发其它应用是我们可以通过申请后台,来获得3分钟的后台执行代码时间(iOS7以前是10分钟)。

所以我们决定申请3分钟来执行我们1分钟的倒计时代码:
<pre>

import "AppDelegate.h"

@interface AppDelegate (){
NSInteger count;
}
@property(strong, nonatomic)NSTimer *mTimer;
@property(assign, nonatomic)UIBackgroundTaskIdentifier backIden;

@end

@implementation AppDelegate

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    count=0;
    return YES;
    }

  • (void)applicationDidEnterBackground:(UIApplication *)application {
    _mTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_mTimer forMode:NSRunLoopCommonModes];
    [self beginTask];
    }

  • (void)applicationWillEnterForeground:(UIApplication *)application {
    NSLog(@"进入前台");
    [self endBack];
    }

//计时
-(void)countAction{
NSLog(@"%li",count++);
}

//申请后台
-(void)beginTask
{
NSLog(@"begin=============");
_backIden = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
//在时间到之前会进入这个block,一般是iOS7及以上是3分钟。按照规范,在这里要手动结束后台,你不写也是会结束的(据说会crash)
NSLog(@"将要挂起=============");
[self endBack];
}];
}

//注销后台
-(void)endBack
{
NSLog(@"end=============");
[[UIApplication sharedApplication] endBackgroundTask:_backIden];
_backIden = UIBackgroundTaskInvalid;
}

@end
</pre>

这样就轻松、完美解决了我们的需求了!!!

3、 扩充无限倒计时

慎用!因为这个需要申请后台播放音频的权限。如果你的应用不是相关应用,AppStore审核可能不会通过。

先在我们的info.plist文件配置中,添加一行,如下两个属性:

Required background modes
App plays audio or streams audio/video using AirPlay

配置信息.png

AppDelegate.m文件中代码还是和上面一样,设置 UIBackgroundTaskIdentifier
<pre>

import "AppDelegate.h"

@interface AppDelegate (){
NSInteger count;
}
@property(strong, nonatomic)NSTimer *mTimer;
@property(assign, nonatomic)UIBackgroundTaskIdentifier backIden;

@end

@implementation AppDelegate

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    count=0;
    return YES;
    }

  • (void)applicationDidEnterBackground:(UIApplication *)application {
    _mTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_mTimer forMode:NSRunLoopCommonModes];
    [self beginTask];
    }

  • (void)applicationWillEnterForeground:(UIApplication *)application {
    NSLog(@"进入前台");
    [self endBack];
    }

//计时
-(void)countAction{
NSLog(@"%li",count++);
}

//申请后台
-(void)beginTask
{
NSLog(@"begin=============");
_backIden = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{

    NSLog(@"将要挂起=============");
    [self endBack];
}];

}

//注销后台
-(void)endBack
{
NSLog(@"end=============");
[[UIApplication sharedApplication] endBackgroundTask:_backIden];
_backIden = UIBackgroundTaskInvalid;
}

@end
</pre>

在我们需要后台运行的控制器中,例如ViewController.m
<pre>

import "ViewController.h"

import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@property(strong, nonatomic)AVAudioPlayer *mPlayer;

@property(assign, nonatomic)CGFloat mCount;

@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    _mCount = 0;

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(countTime) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }

-(void)countTime{
_mCount+=10;
NSLog(@"%f",_mCount);

if ([[UIApplication sharedApplication] backgroundTimeRemaining] < 60.) {//当剩余时间小于60时,开如播放音乐,并用这个假前台状态再次申请后台
    NSLog(@"播放%@",[NSThread currentThread]);
    [self playMusic];
    //申请后台
    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"我要挂起了");
    }];
}

}

-(void)playMusic{
//1.音频文件的url路径,实际开发中,用无声音乐
NSURL *url=[[NSBundle mainBundle]URLForResource:@"欢沁.mp3" withExtension:Nil];

//2.创建播放器(注意:一个AVAudioPlayer只能播放一个url)
_mPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:Nil];

//3.缓冲
[_mPlayer prepareToPlay];

//4.播放
[_mPlayer play];

}

@end
</pre>

完。

自助者,天助之。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 127,637评论 18 546
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 74,695评论 12 116
  • IOS开发之----详解在IOS后台执行 文一 我从苹果文档中得知,一般的应用在进入后台的时候可以获取一定时间来...
    dongfang阅读 773评论 0 7
  • 自从古老的iOS4以来,当用户点击home建的时候,你可以使你的APP们在内存中处于suspended(挂起)状态...
    杨梦鸽阅读 1,051评论 1 5
  • 有许多想做的事情,包括已经在做的了和存留在想法中的,例如跳拉丁、例如写公众号、例如在豆瓣发布活动、例如去找认为有趣...
    黑眼圈圈阅读 26评论 0 0