Minimize Timer Usage
You can reduce your app’s energy usage by implementing energy-efficient APIs instead of timers.
The
NSBackgroundActivityScheduler
API lets you schedule both repeating and non-repeating tasks energy efficiently. See Schedule Background Activity.The
NSURLSession
API provides the ability to perform out-of-process background URL sessions and receive notifications when they are complete. See Schedule Background Networking.The XPC Activity API can be used to request centralized scheduling of discretionary tasks in your app. See Defer Tasks with XPC Activity.
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, as shown in Figure 7-1.
Get Event Notifications Without Using Timers
Some apps use timers to monitor for key presses, mouse locations, 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 7-1.
Objective-C
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);
Swift
let myFile = @"/Path/To/File"
let fileDescriptor = open(myFile.fileSystemRepresentation, O_EVTONLY)
let myQueue = dispatch_get_main_queue()
let dispatchFlags = DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE
let mySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescriptor, dispatchFlags, myQueue)
dispatch_source_set_event_handler(mySource) {
self.checkForFile()
}
dispatch_resume(mySource)
Use event notifications for system-provided services whenever possible. Table 7-1 provides a list of common system notifications along with their corresponding coding approaches.
Event to be notified about |
Approach to follow for obtaining event notifications |
Described in |
---|---|---|
Updates to files |
Configure a dispatch source. |
|
Updates to systemwide files or directories |
Create an event stream with the File System Events API. |
|
Interprocess messages |
Use XPC Services. |
Creating XPC Services in Daemons and Services Programming Guide |
|
Use the |
|
|
Use the |
|
Changes to a disk or volume |
Register for disk arbitration notifications. |
|
The availability of hot-pluggable devices |
Create an IOKit notification object. |
Finding and Accessing Devices in Accessing Hardware From Applications |
Network events |
Use the Apple Push Notification service. |
|
|
Use Bonjour. |
DNS Service Discovery Programming Guide and NSNetServices and CFNetServices Programming Guide |
|
Use the network reachability and connection APIs. |
Determining Reachability and Getting Connected in System Configuration Programming Guidelines |
Events generated by the mouse, keyboard, and other input devices |
Use event monitors. |
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 7-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.
Objective-C
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];
}
Swift
var workIsDone = false
/* thread one */
func doWork() {
/* wait for network ... */
workIsDone = true
}
/* thread two: completion handler */ /*** Not Recommended ***/
func waitForWorkToFinish() {
while (!workIsDone) {
usleep(100000) /* 100 ms */ /*** Not Recommended ***/
}
WorkController.workDidFinish()
}
The code in Listing 7-3 performs synchronization much more efficiently with a serial dispatch queue.
Objective-C
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];
};
Swift
let myQueue = dispatch_queue_create("com.myapp.myq", DISPATCH_QUEUE_SERIAL)
let block = dispatch_block_create(0) {
/* wait for network ... */
}
/* thread one */
func beginWork() {
dispatch_async(myQueue, block)
}
/* thread two */
func waitForWorkToFinish() {
dispatch_block_wait(block, DISPATCH_TIME_FOREVER)
WorkController.workDidFinish()
}
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 7-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.
Objective-C
dispatch_async(thread2_queue) {
/* do long work */
dispatch_async(thread1_queue) {
/* continue with next work */
}
};
Swift
dispatch_async(thread2_queue) {
/* do long work */
dispatch_async(thread1_queue) {
/* continue with next work */
}
}
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. OS X has many programming interfaces that delay processes for specified periods of time. Any method or function to which you pass a relative or absolute deadline is a timer API. For example:
High-level timer APIs include dispatch timer sources,
CFRunLoopTimerCreate
and other CFRunLoopTimer functions, theNSTimer
class, theperformSelector:withObject:afterDelay:
method,CVDisplayLinkStart
and otherCVDisplayLink
functions, and theNSProgressIndicator
class.Low-level timer APIs include the functions
sleep
,usleep
,nanosleep
,pthread_cond_timedwait
,select
,poll
,kevent
,dispatch_after
, anddispatch_semaphore_wait
.
If you determine that your app indeed 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 7-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 7-6 passes the DISPATCH_TIME_FOREVER
constant to dispatch_semaphore_wait
. The function blocks until it receives the semaphore.
Objective-C
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];
}
}
Swift
repeat {
let timeout = dispatch_time(DISPATCH_TIME_NOW, 500 * Double(NSEC_PER_SEC))
let semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout)
if (havePendingWork) {
self.doPendingWork()
}
} while true
Objective-C
while (YES) {
dispatch_time_t timeout = DISPATCH_TIME_FOREVER;
long semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout);
if (havePendingWork) {
[self doPendingWork];
}
}
Swift
repeat {
let timeout = DISPATCH_TIME_FOREVER
let semaphoreReturnValue = dispatch_semaphore_wait(mySemaphore, timeout)
if (havePendingWork) {
self.doPendingWork()
}
} while true
In most cases, blocking indefinitely (as in Listing 7-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 probably wastes more energy than anything else in OS X, and it is one of the simplest problems to fix.
The code in Listing 7-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.
Objective-C
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 */
Swift
var myTimer = NSTimer.initWithFireDate(date, interval: 1.0, target: self, selector:"timerFired:", userInfo:nil repeats: true)
/* Do work until the timer is no longer needed */
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 apps’ 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 7-8. The tolerance of 10 percent is set by setTolerance:0.3
compared to interval:3.0
.
Objective-C
[myTimer setTolerace:0.3];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
Swift
myTimer.tolerance(0.3)
NSRunLoop.currentRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode)
The example in Listing 7-9 shows how you can set a tolerance of 10 percent using the last parameter of the dispatch_source_set_timer
function.
Objective-C
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);
Swift
let myDispatchSourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, myQueue)
dispatch_source_set_timer(myDispatchSourceTimer, DISPATCH_TIME_NOW, 1 * Double(NSEC_PER_SEC), Double(NSEC_PER_SEC) / 10)
dispatch_source_set_event_handler(myDispatchSourceTimer) {
self.timerFired()
}
dispatch_resume(myDispatchSourceTimer)
You can specify a tolerance of 10 percent of the timer interval for CFRunLoop timers using the CFRunLoopTimerSetTolerance
function, as shown in Listing 7-10. The tolerance of 10 percent is set by the second argument to CFRunLoopTimerSetTolerance
, compared with the third argument to CFRunLoopTimerCreate
.
Objective-C
CFRunLoopTimerRef myRunLoopTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, fireDate, 2.0, 0, &timerFired, NULL);
CFRunLoopTimerSetTolerance(myRunLoopTimer, 0.2);
CFRunLoopAddTimer(CFRunLoopGetCurrent(), myRunLoopTimer, kCFRunLoopDefaultMode);
Swift
myRunLoopTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, fireDate, 2.0, 0, 0, &timerFired, NULL)
CFRunLoopTimerSetTolerance(myRunLoopTimer, 0.2)
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 10 percent of the interval for a repeating timer, as in the three examples above. Even a small amount of tolerance has a significant positive impact on the energy usage of your app.
Recognize and Address Timer Issues
While developing and testing your app, you have two tools that can help you determine whether timers in your app are hurting energy performance: the Energy Impact gauge in Xcode and the Energy pane in Activity Monitor.
Energy Impact gauge. When you build and run your app in Xcode, you can check the timer firings with the Energy Impact gauge. Open the debug navigator (choose View > Navigators > Show Debug Navigator). Select the Energy Impact gauge. The Wakes and CPU area reports how often a timer has fired in the last second. A very high number of wakeups results in a very high energy impact, as shown in Figure 7-1.
Activity Monitor. When testing, check timer firings by launching Activity Monitor while your app is running. Click the Energy button in Activity Monitor and choose View > Column > Idle Wake Ups, and view the status of your app. The number in the Idle Wake Ups column reports how many times per second a timer fired, averaged over the sample interval, as illustrated in Figure 7-2.
If your app has more than one wakeup per second when it should be idle, investigate why with the timerfires
command-line tool. Supply the tool with the ID of the process you’re investigating, and pass the -s
flag to enable stack traces. The tool logs all the places where the app has awakened because of a timer. For more information on timerfires
, see timerfires(1) Mac OS X Manual Page.
In the Terminal output shown in Listing 7-11, MyApp has a 2 Hz Core Foundation (CF
) timer that’s executing a routine called timerFired
in its application delegate. A Grand Central Dispatch (dispatch
) timer is also executing a routine called updateWidgets
. MyApp also calls the usleep
timer, as shown in the stack trace. With this information, the developer of MyApp can investigate those calls to see whether they are responsible for excessive wakeups.
$ sudo timerfires -p <pid> -s
TIME(ms) PID PROCESS TYPE TIMER ROUTINE
435 1603 MyApp CF MyApp`-[AppDelegate timerFired:]
933 1603 MyApp CF MyApp`-[AppDelegate timerFired:]
1055 1603 MyApp dispatch MyApp`-[AppModel updateWidgets:]
1435 1603 MyApp CF MyApp`-[AppDelegate timerFired:]
1935 1603 MyApp CF MyApp`-[AppDelegate timerFired:]
2055 1603 MyApp dispatch MyApp`-[AppModel updateWidgets:]
2435 1603 MyApp CF MyApp`-[AppDelegate timerFired:]
3004 1603 MyApp sleep
libsystem_kernel.dylib`__semwait_signal+0xa
libsystem_c.dylib`usleep+0x36
MyApp`-[AppModel pollForChange]+0x1a
libdispatch.dylib`_dispatch_call_block_and_release+0xc
Copyright © 2018 Apple Inc. All rights reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13