最小化计时器使用

您可以通过实现节能api而不是计时器来减少应用程序的能耗。例如,NSURLSession API提供了执行进程外后台URL会话和在会话完成时接收通知的能力。请参见延迟联网。如果你必须使用计时器,就要有效地使用它们。

计时器的高成本

计时器允许您计划延迟或周期性操作。计时器等待到某个时间间隔结束,然后触发,执行特定操作,例如向其目标对象发送消息。当CPU和其他系统从低功耗、空闲状态唤醒时,将系统从空闲状态唤醒会产生能量消耗。如果计时器导致系统唤醒,则会产生该成本。
应用程序经常不必要地使用计时器。如果你在你的应用程序中使用计时器,考虑你是否真的需要它们。例如,有些应用程序在应该响应事件时使用计时器来轮询状态更改。其他应用程序在应该使用信号量或其他锁以获得最大效率时使用计时器作为同步工具。有些计时器在执行时没有适当的超时,导致它们在不再需要时继续触发。不管是哪种情况,如果有许多计时器调用的唤醒,则能量影响很大

不使用计时器获取事件通知

有些应用程序使用计时器监视文件内容、网络可用性和其他状态更改的更改。定时器防止CPU进入或停留在空闲状态,这会增加能耗并消耗电池电量。
与其使用计时器来监视事件,不如使用更高效的服务,例如调度源。见清单5-1。

清单5-1建议:使用节能的调度源获取文件更改通知

const char *myFile = [@"/Path/To/File" fileSystemRepresentation];
int fileDescriptor = open(myFile, O_EVTONLY);
dispatch_queue_t myQueue = dispatch_get_main_queue();
const uint64_t dispatchFlags = DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE;
dispatch_source_t mySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescriptor, dispatchFlags, myQueue);
dispatch_source_set_event_handler(mySource, ^{
    [self checkForFile];
  });
dispatch_resume(mySource);

尽可能对系统提供的服务使用事件通知。表5-1列出了常见系统通知及其相应的编码方法。

表5-1获取系统服务的事件通知

要通知的事件 获取事件通知的方法 描述于
文件更新 配置调度源 Dispatch Sources in Concurrency Programming Guide
对系统范围文件或目录的更新 使用文件系统事件API创建事件流。 File System Programming Guide
网络事件 使用Apple推送通知服务。 Local and Remote Notification Programming Guide
Use Bonjour. DNS Service Discovery Programming Guide and NSNetServices and CFNetServices Programming Guide
使用GCD工具进行同步,而不是使用计时器

Grand Central Dispatch(GCD)提供比计时器更高效的调度队列、调度信号量和其他同步功能。
清单5-2中的代码在一个线程上执行工作,而另一个线程和完成处理程序使用计时器定期检查第一个线程上的工作是否已完成。在第一个线程中的工作完成之前,第二个线程中的usleep计时器只会持续唤醒系统,以确定第一个线程中的工作是否已完成。

清单5-2不推荐:使用能量效率低下的计时器作为同步工具

BOOL workIsDone = NO;
 
/* thread one */
void doWork(void) {
  /* wait for network ... */
  workIsDone = YES;
}
 
/* thread two: completion handler */  /*** Not Recommended ***/
void waitForWorkToFinish(void) {
  while (!workIsDone) {
    usleep(100000); /* 100 ms */  /*** Not Recommended ***/
  }
  [WorkController workDidFinish];
}

清单5-3中的代码使用串行调度队列更有效地执行同步。

myQueue = dispatch_queue_create("com.myapp.myq", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block;
block = dispatch_block_create(0, ^{
    /* wait for network ... */
});
 
/* thread one */
void beginWork(void) {
  dispatch_async(myQueue, block);
};
 
/* thread two */
void waitForWorkToFinish(void) {
  dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
  Block_release(block);
  [WorkController workDidFinish];
};

在不连续唤醒系统的情况下,线程2上的completion方法会等待第一个线程上的工作完成
类似地,清单5-4中的代码演示了如何在一个线程上执行长时间运行的操作,以及在长时间运行的操作完成后在另一个线程上执行额外的工作。例如,此技术可用于防止在应用程序的主线程上发生阻塞工作。

清单5-4推荐:使用异步调度队列在多个线程上执行工作

dispatch_async(thread2_queue) {
  /* do long work */
  dispatch_async(thread1_queue) {
    /* continue with next work */
  }
};

有关使用调度队列和Grand Central dispatch的其他同步功能的更多信息,请看see Concurrency Programming Guide和Grand Central Dispatch (GCD) 参考,也要看 SynchronizationThreading Programming Guide.为了获得最佳的能源效率,需要对调度队列进行分类,以便按系统进行优先级排序。看Specify a QoS for Dispatch Queues and BlocksPrioritize Work with Quality of Service Classes.

如果你必须使用计时器,就要有效地使用它

游戏和其他图形密集型应用程序通常依赖计时器启动屏幕或动画更新。许多编程接口在指定的时间段内延迟进程。传递相对或绝对截止日期的任何方法或函数都可能是计时器API。例如:
高级计时器api包括分派计时器源、CFRunLoopTimerCreate和其他CFRunLoopTimer函数、NSTimer类和performSelector:withObject:afterDelay:method。
低级计时器api包括sleep、usleep、nanosleep、pthread_cond_timedwait、select、poll、kevent、dispatch_after和dispatch_semaphore_wait等函数。

如果您确定您的应用程序需要计时器,请遵循以下准则以获取最少的能量:
通过指定合适的超时,经济地使用计时器。
当不再需要重复计时器时使其失效。
设置计时器应在何时触发的公差。

指定适当的超时

许多函数都包含一个timeout参数,并在达到超时时退出。将一个时间间隔传递给这些函数会使它们作为计时器运行,并消耗计时器的所有能量。
如果你的应用程序使用了不适当的超时值,那么这种使用可能会浪费能量。例如,在清单5-5中的代码中,将当前时间(DISPATCH_Time_NOW)的500纳秒超时值传递给DISPATCH_semaphore_wait函数。在发出信号之前,代码不会执行任何有用的工作,而dispatch_semaphore_wait会持续超时。.如果你的应用程序使用DISPATCH_TIME_FOREVER常量,则该用法可能会无限期地阻止某个函数,从而只允许在需要时恢复该函数。清单5-6中的代码将DISPATCH_TIME_ever常量传递给DISPATCH_semaphore_wait。函数将阻塞,直到收到信号量为止。

清单5-5不推荐:设置应用程序不执行的超时

while (YES) {
  dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_SEC);
  long semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout);
  if (havePendingWork) {
    [self doPendingWork];
  }
}

清单5-6建议:无限期阻塞,直到收到信号量

while (YES) {
  dispatch_time_t timeout = DISPATCH_TIME_FOREVER;
  long semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout);
  if (havePendingWork) {
    [self doPendingWork];
  }
}

在大多数情况下,无限期阻塞(如清单5-6所示)比指定时间值更合适。但是,如果您的应用程序确实需要等待超时,请指定表示有意义的状态更改的信号量值,例如错误条件或网络超时。

使不再需要的重复计时器失效

如果使用重复计时器,请在不再需要时使其无效或取消。忘记停止计时器浪费了大量的精力,是最简单的问题之一。
清单5-7中的代码使用了一个重复的NSTimer计时器。当不再需要计时器时,代码调用invalidate方法来停止计时器再次启动,从而避免不必要的能量消耗。

清单5-7建议:在不再需要计时器时使其失效

NSTimer *myTimer = [[NSTimer alloc] initWithFireDate:date
  interval:1.0
  target:self
  selector:@selector(timerFired:)
  userInfo:nil
  repeats:YES];
/* Do work until the timer is no longer needed */
[myTimer invalidate]; /* Recommended */

对于重复调度计时器,当不再需要时,使用dispatch_source_cancel功能取消计时器。对于重复的CFRunLoop计时器,请使用CFRunLoopTimerInvalidate函数。

指定系统范围内批处理计时器的公差

指定计时器触发时的精度公差。该系统将利用这种灵活性,在其公差范围内将计时器的执行移动少量时间,以便可以同时执行多个计时器。使用这种方法可以显著增加处理器空闲的时间,而用户检测不到系统响应性的变化。
可以使用setTolerance:方法指定计时器的公差,如清单5-8所示。10%的公差由setTolerance:0.3与interval:3.0设置。

清单5-8建议:为NSTimer计时器设置一个公差

[myTimer setTolerace:0.3];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];

清单5-9中的示例显示了如何使用dispatch_source_set_timer函数的最后一个参数设置10%的公差
清单5-9建议:设置分派计时器的公差

dispatch_source_t myDispatchSourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, myQueue);
dispatch_source_set_timer(myDispatchSourceTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, NSEC_PER_SEC / 10);
dispatch_source_set_event_handler(myDispatchSourceTimer, ^{
    [self timerFired];
  }
);
dispatch_resume(myDispatchSourceTimer);

您可以使用CFRunLoopTimerSetTolerance函数为CFRunLoop计时器指定计时器间隔的10%的公差,如清单5-10所示。与CFRunLoopTimerCreate的第三个参数相比,cfrunlooptimersettlerance的第二个参数设置了10%的公差。

清单5-10建议:设置CFRunLoop计时器的公差

CFRunLoopTimerRef myRunLoopTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, fireDate, 2.0, 0, &timerFired,  NULL);
CFRunLoopTimerSetTolerance(myRunLoopTimer, 0.2);
CFRunLoopAddTimer(CFRunLoopGetCurrent(), myRunLoopTimer, kCFRunLoopDefaultMode);

为计时器指定公差后,它可以在其计划开始日期和计划开始日期加上公差之间的任何时间开始。计时器不会在预定的开始日期前开始。对于重复计时器,下一个开始日期总是从原始开始日期开始计算,以便使将来的开始时间与原始计划保持一致。
一般准则是将公差设置为重复计时器间隔的至少10%,如以上示例所示。即使是少量的容忍度也会对应用程序的能量使用产生显著的积极影响。
你不能指望计时器在你要求的精确纳秒内开始,即使没有公差。这个系统尽力满足你的需要,但不能保证准确的开始时间。系统甚至可以对没有指定数量的计时器应用少量的公差,但该公差通常不足以允许在一次唤醒期间触发多个计时器。

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

推荐阅读更多精彩内容