iOS后台运行计时器

最近做的一个项目,主要关于效率类的,涉及到得主要功能也就是计时器。如果只是一个简单的计时器,应该是不费事的,但是这次项目中的计时器有几个必须实现的部分:一个是长时间后台运行,另一个是有周期,到时提醒。这些刚开始做的时候感觉很简单,计时器直接套用一个MZTimerLabel这个类,实现正反计时很方便,代码如下:


//计时器

timer1= [[MZTimerLabelalloc]initWithFrame:CGRectMake(0,kScreenHeight*.24,self.view.frame.size.width,40)];

[selfsetTime:timer1];

[self.viewaddSubview:timer1];

- (void)setTime:(MZTimerLabel*)time{

time.timerType=MZTimerLabelTypeTimer;

time.timeLabel.backgroundColor= [UIColorclearColor];

time.timeLabel.textColor= [UIColorwhiteColor];

time.timeLabel.font= [UIFontfontWithName:Gongtisize:28];

time.timeFormat=@"mm : ss";

time.timeLabel.textAlignment=NSTextAlignmentCenter;

time.resetTimerAfterFinish=YES;

}

接下来就要实现周期循环,计时提醒的功能,因为之前对计时器了解不多,用起来不是很得心应手。最开始涉及循环这块,我直接采用了

dispatch_async(dispatch_get_global_queue(0,0), ^{
        dispatch_apply([self.count integerValue], dispatch_get_main_queue(), myBlock);
        [[NSRunLoop currentRunLoop]run];
    });

但是这个方法无法实现计时器的重复运行,这一点让我很废神,但因为赶的紧,所以直接用了NSTimer的一个方法:

[NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(runTime) userInfo:nil repeats:YES];

这个方法的缺陷在于周期必须固定,如果一旦某一段任务时间执行超出,那么就会产生代码执行混乱。这里产生的问题,让我一直再想有没有更好的方法,后来就采用了更简单粗暴的方式,方法里套用方法,每执行一次,计数加一,直到指定的周期停止。到这里,循环的问题解决了,后来我就加了声音提醒,手机震动之类的,这里插播一下系统手机声音,震动的方法:先导入AudioToolBox.framework;然后在需要调用的文件里#import <AudioToolbox/AudioToolbox.h>,再添加以下两句代码就可以实现提示音播放和震动了

  AudioServicesPlaySystemSound(1005);
  AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

如果想自己添加音乐,可以看下如下链接http://www.blogjava.net/writegull/archive/2012/04/16/374729.html

本来以为到这应该就结束了,结果出现了如下问题:一是无法长时间运行,二是只要计时器置于后台,声音,震动都无法执行。这些问题本来就是iOS自身的问题,因为应用置于后台后,不久就会挂起,大概时间是10分钟。刚开始没想到什么比较好的方法,就在网上查了一些后台长时间运行的方法,尝试了添加后台音频模式,位置更新,但这两种一是没有成功,再个是应用审查可能会被挂掉。一筹莫展,等到第二天起床的时候,闹铃响起,我看了看手机,才想到可以用本地通知的方式啊,怎么之前没想到,应该是从来没用过的原因。本地通知用起来很方便,到时会有提醒之类的,简单写下相关代码

//设置本地通知
+ (void)registerLocalNotification:(NSInteger)alertTime words:(NSString *)words{
    UILocalNotification * notification  = [[UILocalNotification alloc]init];
    //设置触发通知的时间
    NSDate * fireDate = [NSDate dateWithTimeIntervalSinceNow:alertTime];
    notification.fireDate = fireDate;
    //时区
    notification.timeZone = [NSTimeZone defaultTimeZone];
    //设置重复的间隔
  //  notification.repeatInterval = kCFCalendarUnitSecond;
    
    //通知内容
    notification.alertBody = words;
    notification.applicationIconBadgeNumber = 1;
    //通知触发播放的声音
    notification.soundName = UILocalNotificationDefaultSoundName;
    //通知参数
    NSDictionary * userDict= [NSDictionary dictionaryWithObject:words forKey:@"key"];
    notification.userInfo = userDict;
    
    // ios8后,需要添加这个注册,才能得到授权
    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIUserNotificationType type =  UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type
                                                                                 categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
        // 通知重复提示的单位,可以是天、周、月
        notification.repeatInterval = NSCalendarUnitDay;
    } else {
        // 通知重复提示的单位,可以是天、周、月
        notification.repeatInterval = NSCalendarUnitDay;
    }
    
    // 执行通知注册
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
 
}
// 取消某个本地推送通知
+ (void)cancelLocalNotificationWithKey:(NSString *)key {
    // 获取所有本地通知数组
    NSArray *localNotifications = [UIApplication sharedApplication].scheduledLocalNotifications;
    for (UILocalNotification *notification in localNotifications) {
        NSDictionary *userInfo = notification.userInfo;
        if (userInfo) {
            // 根据设置通知参数时指定的key来获取通知参数
            NSString *info = userInfo[key];
            // 如果找到需要取消的通知,则取消
            if (info != nil) {
                [[UIApplication sharedApplication] cancelLocalNotification:notification];
                break;
            }
        }
    }
}
//取消特定通知
[jishiVC cancelLocalNotificationWithKey:@"key"];
//取消全部
[[UIApplication sharedApplication]cancelAllLocalNotifications];

// 本地通知回调函数,当应用程序在前台时调用
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    // 获取通知所带的数据
    NSString *notMess = [notification.userInfo objectForKey:@"key"];
    UIAlertController * alert=[UIAlertController alertControllerWithTitle:nil message:notMess preferredStyle:UIAlertControllerStyleAlert];
    [self.window.rootViewController presentViewController:alert animated:YES completion:^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
        });
    }];
    // 更新显示的徽章个数
    NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;
    badge--;
    badge = badge >= 0 ? badge : 0;
    [UIApplication sharedApplication].applicationIconBadgeNumber = badge;
    // 在不需要再推送时,可以取消推送
    [jishiVC cancelLocalNotificationWithKey:@"key"];
}

但是时间长了以后,即使提醒,计时器还是挂掉了,页面也不在了...
唯有重写计时器页面,每次点进去都要刷新一遍计时,但是这样也会导致一些问题,因为有两个计时页面,后台挂起后,应用重新进入要判断哪个计时页面,计时部分也要做大量的判断,因为会被分成不同阶段,然后有不同的提示。刚开始这么写的时候,很乱。后来就想试试看苹果的计时器是怎么做的,然后用手机自带的计时器设置了半小时的时长,半小时后再打开计时器,页面还在,无论中途打开也依旧如此,这个里边一定有鬼,于是在网上查了相关的资料,发现了如下两段代码,加进去之后,有如神来之笔,计时器可以一段时间内运行,页面不会挂掉:

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appGoToBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];

 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appHasGoneInForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];

查了一下原因:
UIApplicationDidEnterBackgroundNotification(通知名称)----->应用程序在此方法中释放所有可在以后重新创建的资源,保存所有用户数据,关闭网络连接等。如果需要,也可以在这里请求在后台运行更长时间。如果在这里花费了太长时间(超过5秒),系统将断定应用程序的行为异常并终止他。
UIApplicationWillEnterForegroundNotification(通知名称) ---->当应用程序在applicationDidEnterBackground:花费了太长时间,终止后,应该实现此方法来重新创建在applicationDidEnterBackground中销毁的内容,比如重新加载用户数据、重新建立网络连接等。

这是项目中仅仅计时器这块出现的问题,虽然某些问题解决了,但是深层次的原因还是不清楚,希望有了解此类问题的童鞋能多多指教。。。

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

推荐阅读更多精彩内容