Documentation Archive

Developer

Energy Efficiency Guide for iOS Apps

On This Page

Minimize Timer Use

You can reduce your app’s energy usage by implementing energy-efficient APIs instead of timers. The NSURLSession API, for example, provides the ability to perform out-of-process background URL sessions and receive notifications when they are complete. See Defer Networking. If you must use timers, employ them efficiently.

The High Cost of Timers

A timer lets you schedule a delayed or periodic action. A timer waits until a certain interval has elapsed and then fires, performing a specific action such as sending a message to its target object. Waking the system from an idle state incurs an energy cost when the CPU and other systems are awakened from their low-power, idle states. If a timer causes the system to wake, it incurs that cost.

Apps often use timers unnecessarily. If you use timers in your app, consider whether you truly need them. For example, some apps use timers to poll for state changes when they should respond to events instead. Other apps use timers as synchronization tools when they should use semaphores or other locks to achieve the greatest efficiency. Some timers are executed without suitable timeouts, causing them to continue firing when they’re no longer needed. Regardless of the scenario, if there are many timer-invoked wakeups, the energy impact is high.

Get Event Notifications Without Using Timers

Some apps use timers to monitor for changes to file contents, network availability, and other state changes. Timers prevent the CPU from going to or staying in the idle state, which increases energy usage and consumes battery power.

Instead of using timers to watch for events, use a more efficient service, such as a dispatch source. See Listing 5-1.

Listing 5-1Recommended: Use an energy-efficient dispatch source to obtain file change notifications

Objective-C

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

Swift

  1. let myFile = @"/Path/To/File"
  2. let fileDescriptor = open(myFile.fileSystemRepresentation, O_EVTONLY)
  3. let myQueue = dispatch_get_main_queue()
  4. let dispatchFlags = DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE
  5. let mySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescriptor, dispatchFlags, myQueue)
  6. dispatch_source_set_event_handler(mySource) {
  7. self.checkForFile()
  8. }
  9. dispatch_resume(mySource)

Use event notifications for system-provided services whenever possible. Table 5-1 provides a list of common system notifications along with their corresponding coding approaches.

Table 5-1Obtaining event notifications for system services

Event to be notified about

Approach to follow for obtaining event notifications

Described in

Updates to files

Configure a dispatch source.

Dispatch Sources in Concurrency Programming Guide

Updates to systemwide files or directories

Create an event stream with the File System Events API.

File System Programming Guide

Network events

Use the Apple Push Notification service.

Local and Remote Notification Programming Guide

Use Bonjour.

DNS Service Discovery Programming Guide and NSNetServices and CFNetServices Programming Guide

Use GCD Tools for Synchronization Instead of Timers

Grand Central Dispatch (GCD) provides dispatch queues, dispatch semaphores, and other synchronization features that are more efficient than timers.

The code in Listing 5-2 performs work on one thread while another thread and a completion handler use a timer to periodically check whether work on the first thread has completed. Until the work in the first thread is completed, the usleep timer in the second thread continually wakes the system only to determine whether the work in the first thread has completed.

Listing 5-2Not recommended: Using an energy-inefficient timer as a synchronization tool

Objective-C

  1. BOOL workIsDone = NO;
  2. /* thread one */
  3. void doWork(void) {
  4. /* wait for network ... */
  5. workIsDone = YES;
  6. }
  7. /* thread two: completion handler */ /*** Not Recommended ***/
  8. void waitForWorkToFinish(void) {
  9. while (!workIsDone) {
  10. usleep(100000); /* 100 ms */ /*** Not Recommended ***/
  11. }
  12. [WorkController workDidFinish];
  13. }

Swift

  1. var workIsDone = false
  2. /* thread one */
  3. func doWork() {
  4. /* wait for network ... */
  5. workIsDone = true
  6. }
  7. /* thread two: completion handler */ /*** Not Recommended ***/
  8. func waitForWorkToFinish() {
  9. while (!workIsDone) {
  10. usleep(100000) /* 100 ms */ /*** Not Recommended ***/
  11. }
  12. WorkController.workDidFinish()
  13. }

The code in Listing 5-3 performs synchronization much more efficiently with a serial dispatch queue.

Listing 5-3Recommended: Using an energy-efficient dispatch queue to synchronize threads

Objective-C

  1. myQueue = dispatch_queue_create("com.myapp.myq", DISPATCH_QUEUE_SERIAL);
  2. dispatch_block_t block;
  3. block = dispatch_block_create(0, ^{
  4. /* wait for network ... */
  5. });
  6. /* thread one */
  7. void beginWork(void) {
  8. dispatch_async(myQueue, block);
  9. };
  10. /* thread two */
  11. void waitForWorkToFinish(void) {
  12. dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
  13. Block_release(block);
  14. [WorkController workDidFinish];
  15. };

Swift

  1. let myQueue = dispatch_queue_create("com.myapp.myq", DISPATCH_QUEUE_SERIAL)
  2. let block = dispatch_block_create(0) {
  3. /* wait for network ... */
  4. }
  5. /* thread one */
  6. func beginWork() {
  7. dispatch_async(myQueue, block)
  8. }
  9. /* thread two */
  10. func waitForWorkToFinish() {
  11. dispatch_block_wait(block, DISPATCH_TIME_FOREVER)
  12. WorkController.workDidFinish()
  13. }

Without continually waking the system, the completion method on thread two waits for the work on the first thread to finish.

Similarly, the code in Listing 5-4 demonstrates how to perform a long-running operation on one thread, and additional work on another thread once the long-running operation completes. This technique could be used, for example, to prevent blocking work from occurring on the main thread of your app.

Listing 5-4Recommended: Using asynchronous dispatch queues to perform work on multiple threads

Objective-C

  1. dispatch_async(thread2_queue) {
  2. /* do long work */
  3. dispatch_async(thread1_queue) {
  4. /* continue with next work */
  5. }
  6. };

Swift

  1. dispatch_async(thread2_queue) {
  2. /* do long work */
  3. dispatch_async(thread1_queue) {
  4. /* continue with next work */
  5. }
  6. }

If You Must Use a Timer, Employ It Efficiently

Games and other graphics-intensive apps often rely on timers to initiate screen or animation updates. Many programming interfaces delay processes for specified periods of time. Any method or function to which you pass a relative or absolute deadline is probably a timer API. For example:

If you determine that your app requires a timer, follow these guidelines for drawing the least amount of energy:

  • Use timers economically by specifying suitable timeouts.

  • Invalidate repeating timers when they’re no longer needed.

  • Set tolerances for when timers should fire.

Specify Suitable Timeouts

Many functions include a timeout parameter and exit when the timeout is reached. Passing a time interval to these functions causes them to operate as timers, with all the energy consumption of timers.

If your app uses an inappropriate timeout value, that usage can waste energy. For example, in the code in Listing 5-5, a timeout value of 500 nanoseconds from the current time (DISPATCH_TIME_NOW) is passed to the dispatch_semaphore_wait function. Until the semaphore is signaled, the code performs no useful work while dispatch_semaphore_wait continually times out.

If your app uses the DISPATCH_TIME_FOREVER constant, that usage can block a function indefinitely, allowing the function to resume only when needed. The code in Listing 5-6 passes the DISPATCH_TIME_FOREVER constant to dispatch_semaphore_wait. The function blocks until it receives the semaphore.

Listing 5-5Not recommended: Set a timeout that isn’t acted upon by the app

Objective-C

  1. while (YES) {
  2. dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_SEC);
  3. long semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout);
  4. if (havePendingWork) {
  5. [self doPendingWork];
  6. }
  7. }

Swift

  1. repeat {
  2. let timeout = dispatch_time(DISPATCH_TIME_NOW, 500 * Double(NSEC_PER_SEC))
  3. let semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout)
  4. if (havePendingWork) {
  5. self.doPendingWork()
  6. }
  7. } while true
Listing 5-6Recommended: Block indefinitely until a semaphore is received

Objective-C

  1. while (YES) {
  2. dispatch_time_t timeout = DISPATCH_TIME_FOREVER;
  3. long semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout);
  4. if (havePendingWork) {
  5. [self doPendingWork];
  6. }
  7. }

Swift

  1. repeat {
  2. let timeout = DISPATCH_TIME_FOREVER
  3. let semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout)
  4. if (havePendingWork) {
  5. self.doPendingWork()
  6. }
  7. } while true

In most cases, blocking indefinitely (as in Listing 5-6) is more suitable than specifying a time value. But if your app does need to wait for a timeout, specify a semaphore value that represents a meaningful state change, such as an error condition or a network timeout.

Invalidate Repeating Timers You No Longer Need

If you use a repeating timer, invalidate or cancel it when you no longer need it. Forgetting to stop timers wastes lots of energy, and is one of the simplest problems to fix.

The code in Listing 5-7 uses a repeating NSTimer timer. When the timer is no longer needed, the code calls the invalidate method to stop the timer from firing again, avoiding unnecessary energy use.

Listing 5-7Recommended: Invalidating a timer when it is no longer needed

Objective-C

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

Swift

  1. var myTimer = NSTimer.initWithFireDate(date, interval: 1.0, target: self, selector:"timerFired:", userInfo:nil repeats: true)
  2. /* Do work until the timer is no longer needed */
  3. myTimer.invalidate() /* Recommended */

For a repeating dispatch timer, use the dispatch_source_cancel function to cancel the timer when it’s no longer needed. For a repeating CFRunLoop timer, use the CFRunLoopTimerInvalidate function.

Specify a Tolerance for Batching Timers Systemwide

Specify a tolerance for the accuracy of when your timers fire. The system will use this flexibility to shift the execution of timers by small amounts of time—within their tolerances—so that multiple timers can be executed at the same time. Using this approach dramatically increases the amount of time that the processor spends idling while users detect no change in system responsiveness.

You can use the setTolerance: method to specify a tolerance for your timer, as shown in Listing 5-8. The tolerance of ten percent is set by setTolerance:0.3 compared to interval:3.0.

Listing 5-8Recommended: Set a tolerance for NSTimer timers

Objective-C

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

Swift

  1. myTimer.tolerance(0.3)
  2. NSRunLoop.currentRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode)

The example in Listing 5-9 shows how you can set a tolerance of ten percent using the last parameter of the dispatch_source_set_timer function.

Listing 5-9Recommended: Set a tolerance for dispatch timers

Objective-C

  1. dispatch_source_t myDispatchSourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, myQueue);
  2. dispatch_source_set_timer(myDispatchSourceTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, NSEC_PER_SEC / 10);
  3. dispatch_source_set_event_handler(myDispatchSourceTimer, ^{
  4. [self timerFired];
  5. }
  6. );
  7. dispatch_resume(myDispatchSourceTimer);

Swift

  1. let myDispatchSourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, myQueue)
  2. dispatch_source_set_timer(myDispatchSourceTimer, DISPATCH_TIME_NOW, 1 * Double(NSEC_PER_SEC), Double(NSEC_PER_SEC) / 10)
  3. dispatch_source_set_event_handler(myDispatchSourceTimer) {
  4. self.timerFired()
  5. }
  6. dispatch_resume(myDispatchSourceTimer)

You can specify a tolerance of ten percent of the timer interval for CFRunLoop timers using the CFRunLoopTimerSetTolerance function, as shown in Listing 5-10. The tolerance of ten percent is set by the second argument to CFRunLoopTimerSetTolerance, compared with the third argument to CFRunLoopTimerCreate.

Listing 5-10Recommended: Set a tolerance for CFRunLoop timers

Objective-C

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

Swift

  1. myRunLoopTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, fireDate, 2.0, 0, 0, &timerFired, NULL)
  2. CFRunLoopTimerSetTolerance(myRunLoopTimer, 0.2)
  3. CFRunLoopAddTimer(CFRunLoopGetCurrent(), myRunLoopTimer, kCFRunLoopDefaultMode)

After you specify a tolerance for a timer, it may fire anytime between its scheduled fire date and the scheduled fire date, plus the tolerance. The timer won’t fire before the scheduled fire date. For repeating timers, the next fire date is always calculated from the original fire date in order to keep future fire times on track with their original schedule.

A general guideline is to set the tolerance to at least ten percent of the interval for a repeating timer, as in the examples above. Even a small amount of tolerance has a significant positive impact on the energy usage of your app.