Sign in Join
Wiki copytemporary

macOS App Nap: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Overview
22
33macOS 10.9 (Mavericks) introduced substantial power-saving features under the App Nap umbrella, especially for laptops. App Nap is based on several key principles:
44
55- The features work without the developer needing to modify existing applications.
66- The features keep the hardware as idle as possible given the demand for resources.
77- When a Mac is on battery power, only the work the user requests or that is absolutely essential is done.
88
99App Nap is activated by the operating system when:
1010
1111- The application's windows are not visible; eg minimised to the dock or hidden by another application's window(s).
1212- The application has not updated any visible part of an open window for some time.
1313- The application is not playing audio.
1414- The application is not using OpenGL graphics.
1515- The application is in the background.
1616- The application has not informed macOS that it is still active via IOKit power management or NSProcessInfo assertions.
1717
1818The App Nap power-saving measures include:
1919
2020- **Timer throttling**: The frequency with which an application's timers are executed is reduced, increasing CPU idle time when running applications that, for example, check for data.
2121
2222- **I/O throttling**: Disk and network activity is assigned the lowest priority for applications that are "napping" thereby reducing the speed at which the application can read/write data from/to a device. This also reduces the likelihood that a napping application will impact an application which is actively being used.
2323
2424- **Priority reduction**: The UNIX process priority of an application is reduced so that it receives less available CPU time.
2525
2626### Timer coalescing
2727
2828Timer coalescing, although not an App Nap feature, was introduced in Mavericks at the same time. To maximise the amount of time that the CPU spends at idle, timer coalescing shifts the execution of timers by a small amount so that timers of multiple applications are executed at the same time. This is done by applying a time window to every timer based on the importance of the process. A timer can be executed at any time during this window, so may be shifted backward or forward a small amount so that it lines up with other timers that need to be executed at similar times. The timer windows are:
2929
3030| Process type | Timer window |
3131|----------------------------|--------------|
3232| Application (default) | 1ms |
3333| System daemon | 70-90ms |
3434| Background process | 80-120ms |
3535| Critical/Real time process | 0ms |
3636
3737The NSTimer class in Mavericks introduced a new *tolerance* parameter which enables developers to tune how timely timer-event driven events need to be.
3838
3939## Managing App Nap
4040
4141App Nap introduced a new API in *NSProcessInfo* which gives developers the ability to inform the operating system when an application is performing a long-running operation that may need to prevent App Nap or system sleep.
4242
4343``` pascal
4444NSProcessInfoActivity = objccategory external (NSProcessInfo)
4545 function beginActivityWithOptions_reason (options: NSActivityOptions; reason: NSString): NSObject; message 'beginActivityWithOptions:reason:';
4646 procedure endActivity (activity: NSObject); message 'endActivity:';
4747 procedure performActivityWithOptions_reason_usingBlock (options: NSActivityOptions; reason: NSString; block: OpaqueCBlock); message 'performActivityWithOptions:reason:usingBlock:';
4848```
4949
5050Be aware that failing to execute *endActivity* after a *beginActivityWithOptions* for an extended period of time can have significant negative impacts on the performance of your user's computer, so only use the minimum amount of time required.
5151
5252### Automatic termination
5353
5454Automatic termination is a feature introduced in macOS 10.7 (Lion) that you must explicitly code for in your application. If it is enabled, then your application needs to save the current state of its user interface so that it can be restored later . The system can kill the underlying process for an auto-terminable application at any time, so saving this information maintains application continuity. The system usually kills an application’s underlying process some time after the user has closed all of its windows. However, the system may also kill an application with open windows if it is not currently visible on the screen.
5555
5656To support automatic termination, you need to:
5757
5858- Declare support for automatic termination either programmatically or setting the *NSSupportsAutomaticTermination* key value to YES in the application's `Info.plist` file.
5959- Support saving and restoring the application's window configurations.
6060- Save the user’s data at appropriate times.
6161 - Single-window, library-style applications should implement strategies for saving data at appropriate checkpoints.
6262 - Multiwindow, document-based applications can use the autosaving and saveless documents capabilities in *NSDocument*.
6363
6464You can also use the App Nap API to control automatic termination.
6565
6666``` pascal
6767function beginActivityWithOptions_reason (options: NSActivityOptions; reason: NSString): NSObjectProtocol; message 'beginActivityWithOptions:reason:';
6868// Perform some task
6969procedure endActivity (activity: NSObjectProtocol); message 'endActivity:';
7070```
7171
7272is equivalent to:
7373
7474``` pascal
7575procedure disbleAutomaticTermination (reason: NSString);
7676// Perform some task
7777procedure enableAutomaticTermination (reason: NSString);
7878```
7979
8080## NSActivityOptions
8181
8282| Option | Effect |
8383|----|----|
8484| NSActivityIdleDisplaySleepDisabled | require the screen to stay powered on |
8585| NSActivityIdleSystemSleepDisabled | prevent idle sleep |
8686| NSActivitySuddenTerminationDisabled | prevent sudden termination |
8787| NSActivityAutomaticTerminationDisabled | prevent automatic termination |
8888| NSActivityUserInitiated | indicate the application is performing a user-requested action |
8989| NSActivityUserInitiatedAllowingIdleSystemSleep | indicate the application is performing a user-requested action, but that the system can sleep on idle |
9090| NSActivityBackground | indicate the application has initiated some kind of work, but not as the direct result of user request |
9191| NSActivityLatencyCritical | indicate the activity requires the highest amount of timer and I/O precision available |
9292
9393### Recommended usage
9494
9595**NSActivityUserInitiated**: Used for finite length activities that the user has explicitly started. Examples include exporting or downloading a user specified file.
9696
9797**NSActivityBackground**: Used for finite length activities that are part of the normal operation of your application but are not explicitly started by the user. Examples include autosaving, indexing, and automatic downloading of files.
9898
9999**NSActivityLatencyCritical**: If your application requires high priority I/O, you can include the flag (using a bitwise OR). You should only use this flag for activities like audio or video recording that really do require high priority.
100100
101101## Example code
102102
103103``` pascal
104104unit Unit1;
105105
106106{$mode objfpc}{$H+}
107107{$modeswitch objectivec1}
108108
109109interface
110110
111111uses
112112 Classes, SysUtils, Forms, Dialogs, StdCtrls, ExtCtrls, CocoaAll;
113113
114114{ NSProcessInfo }
115115
116116type
117117 NSProcessInfoActivity = objccategory external (NSProcessInfo)
118118 function beginActivityWithOptions_reason (options: NSActivityOptions; reason: NSString): NSObjectProtocol; message 'beginActivityWithOptions:reason:'; { available in 10_9, 7_0 }
119119 procedure endActivity (activity: NSObjectProtocol); message 'endActivity:'; { available in 10_9, 7_0 }
120120 procedure performActivityWithOptions_reason_usingBlock (options: NSActivityOptions; reason: NSString; block: OpaqueCBlock); message 'performActivityWithOptions:reason:usingBlock:'; { available in 10_9, 7_0 }
121121 end;
122122
123123 { TForm1 }
124124
125125 TForm1 = Class(TForm)
126126 Button1: TButton;
127127 Button2: TButton;
128128 Button3: TButton;
129129 Memo1: TMemo;
130130 procedure Button1Click(Sender: TObject);
131131 procedure Button2Click(Sender: TObject);
132132 procedure Button3Click(Sender: TObject);
133133 private
134134 public
135135 end;
136136
137137var
138138 Form1: TForm1;
139139 myActivityToken: NSObjectProtocol;
140140
141141implementation
142142
143143{$R *.lfm}
144144
145145procedure TForm1.Button1Click(Sender: TObject);
146146begin
147147 Form1.Memo1.Append('beginActivity');
148148
149149 myActivityToken := NSProcessInfo.processInfo.beginActivityWithOptions_reason((NSActivityIdleSystemSleepDisabled or NSActivityUserInitiated or NSActivityAutomaticTerminationDisabled), NSSTR('No napping!'));
150150
151151 if(myActivityToken = Nil) then
152152 begin
153153 Form1.Memo1.Append('No Token!');
154154 end;
155155
156156 myActivityToken.retain;
157157end;
158158
159159procedure TForm1.Button2Click(Sender: TObject);
160160begin
161161 Form1.Memo1.Append('endActivity!');
162162
163163 if (myActivityToken <> Nil) then
164164 begin
165165 NSProcessinfo.processinfo.endActivity(myActivityToken);
166166 myActivityToken.release;
167167 myActivityToken := Nil;
168168 end;
169169end;
170170
171171procedure TForm1.Button3Click(Sender: TObject);
172172begin
173173 Close;
174174end;
175175
176176end.
177177```
178178
179179## App Nap status
180180
181181There are two ways of checking the App Nap status of your application.
182182
183183### Method 1: Activity Monitor
184184
185185Open Applications \> Utilities \> Activity Monitor which shows the status of App Nap for all running applications as well as other useful information like the energy impact of each application on the system:
186186
187187![](/wiki-images/f/f9/Activity_Monitor_app_Nap.jpg)
188188
189189### Method 2: pmset
190190
191191Open an Applications \> Utilities \> Terminal and type the command:
192192
193-`pmset -g assertionslog`
193+```text
194+pmset -g assertionslog
195+```
194196
195197which will produce the following output when you run the example code above and click the *BeginActivity* and *EndActivity* buttons.
196198
197-`Time             Action      Type                          PID(Causing PID)    ID                  Name                                              `\
198-`====             ======      ====                          ================    ==                  ====                                              `\
199-`02/13 16:35:19   Created     PreventUserIdleSystemSleep    1188                0x40e40001880f      No napping!                                       `\
200-`02/13 16:35:19   System wide status: PreventUserIdleSystemSleep: 1  `\
201-`02/13 16:35:29   Released    PreventUserIdleSystemSleep    1188                0x40e40001880f      No napping!                                       `\
202-`02/13 16:35:29   System wide status: PreventUserIdleSystemSleep: 0`
199+``` pascal
200+Time Action Type PID(Causing PID) ID Name
201+==== ====== ==== ================ == ====
202+02/13 16:35:19 Created PreventUserIdleSystemSleep 1188 0x40e40001880f No napping!
203+02/13 16:35:19 System wide status: PreventUserIdleSystemSleep: 1
204+02/13 16:35:29 Released PreventUserIdleSystemSleep 1188 0x40e40001880f No napping!
205+02/13 16:35:29 System wide status: PreventUserIdleSystemSleep: 0
206+```
203207
204208## Enhancing App Nap
205209
206210While App Nap operates automatically, with very little effort you can enhance its capabilities by actively reducing your application's energy footprint and further lengthening the time users can use their battery-powered devices without needing to plug them into the power.
207211
208212Your application knows best about the importance of its activity and should not rely on App Nap to put it into an idle state. The most effective way to enhance App Nap is for your application to listen for notifications that it’s no longer in active use and to reduce or suspend energy-intensive work as quickly as possible which can happen well before the system triggers App Nap automatically.
209213
210214### Implement active application transition delegate methods
211215
212216Implement NSApplicationDelegate methods in your application delegate to receive calls when the active state of your application — whether it’s in the foreground or not — changes.
213217
214218| Method | When sent by the default notification center | Significance |
215219|----|----|----|
216220| applicationWillResignActive | Immediately before the application is deactivated and no longer the foreground (frontmost) application. | Start winding down activity that can be stopped entirely once the change in state has completed. |
217221| applicationDidResignActive | Immediately after the application is deactivated and gives up its position as the foreground application. | Stop any power-intensive operations, animations, and UI updates as much as possible. |
218222| applicationWillBecomeActive | Immediately before the application becomes active and comes to the front. | Start resuming operations. |
219223| applicationDidBecomeActive | Immediately after the application becomes active. | Fully resume operations that were reduced or halted. |
220224
221225NSApplicationDelegate methods
222226
223227#### Example code
224228
225229In the example code below the relevant delegate methods simply log their occurrence. In a real application you would need to replace them with the desired behaviours to reduce or suspend relevant activities and restart activities as required.
226230
227231``` pascal
228232unit Unit1;
229233
230234{$mode objfpc}{$H+}
231235{$modeswitch objectivec1}
232236
233237interface
234238
235239uses
236240 Forms, StdCtrls, CocoaAll;
237241
238242type
239243
240244 { TForm1 }
241245
242246 TForm1 = class(TForm)
243247 Button1: TButton;
244248 procedure Button1Click(Sender: TObject);
245249 procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
246250 procedure FormCreate(Sender: TObject);
247251 private
248252
249253 public
250254
251255 end;
252256
253257 {TMyAppDelegate}
254258
255259 TMyAppDelegate = objcclass(NSObject)
256260 private
257261
258262 public
259263 procedure applicationWillBecomeActive (aNotification: NSNotification);
260264 message 'applicationWillBecomeActive:';
261265 procedure applicationDidBecomeActive (aNotification: NSNotification);
262266 message 'applicationDidBecomeActive:';
263267 procedure applicationWillResignActive (aNotification: NSNotification);
264268 message 'applicationWillResignActive:';
265269 procedure applicationDidResignActive (aNotification: NSNotification);
266270 message 'applicationDidResignActive:';
267271 end;
268272
269273var
270274 Form1: TForm1;
271275 myAppDelegate: TMyAppDelegate;
272276
273277implementation
274278
275279{$R *.lfm}
276280
277281{NSApplication Delegates}
278282
279283procedure TMyAppDelegate.applicationWillBecomeActive(aNotification: NSNotification);
280284begin
281285 NSLog(NSStr('will become active'));
282286end;
283287
284288procedure TMyAppDelegate.applicationDidBecomeActive(aNotification: NSNotification);
285289begin
286290 NSLog(NSStr('did become active'));
287291end;
288292
289293procedure TMyAppDelegate.applicationWillResignActive (aNotification: NSNotification);
290294begin
291295 NSLog(NSStr('will resign active'));
292296end;
293297procedure TMyAppDelegate.applicationDidResignActive (aNotification: NSNotification);
294298begin
295299 NSLog(NSStr('did resign active'));
296300end;
297301
298302{Button Events}
299303
300304procedure TForm1.Button1Click(Sender: TObject);
301305begin
302306 NSLog(NSStr('Closing'));
303307 Close;
304308end;
305309
306310{Form Events}
307311
308312procedure TForm1.FormCreate(Sender: TObject);
309313begin
310314 // NSApp
311315 // - The global variable for the shared application instance.
312316 // NSApplication.sharedApplication
313317 // - Returns the application instance, creating it if it doesn’t exist yet.
314318 NSApp := NSApplication.sharedApplication;
315319 NSApp.setDelegate(myAppDelegate);
316320end;
317321
318322procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
319323begin
320324 if(NSApp.isActive) then
321325 begin
322326 NSLog(NSStr('Exiting'));
323327 exit;
324328 end
325329 else
326330 // needed if any part of form is occluded or does not quit
327331 // until dock icon clicked when quitting via dock menu
328332 begin
329333 NSLog(NSStr('Closing2'));
330334 Application.BringToFront;
331335 CloseAction := TCloseAction.caFree;
332336 end;
333337end;
334338
335339Initialization
336340 NSLog(NSStr('initialization'));
337341 myAppDelegate := TMyAppDelegate.alloc.init;
338342
339343Finalization
340344 NSLog(NSStr('finalization'));
341345 myAppDelegate.Release;
342346end.
343347```
344348
345349Typical log output when the executable is started from an Applications \> Utilities \> Terminal:
346350
347-`trev@macmini7 [/Users/trev/Programming/LAZARUS/laz_appnap_ex1] $ ./project1.app/Contents/MacOS/project1`\
348-`2021-03-01 17:57:40.293 project1[1695:130876] initialization`\
349-`2021-03-01 17:57:40.378 project1[1695:130876] will become active`\
350-`2021-03-01 17:57:40.379 project1[1695:130876] did become active`\
351-`2021-03-01 17:57:43.206 project1[1695:130876] will resign active`\
352-`2021-03-01 17:57:43.207 project1[1695:130876] did resign active`\
353-`2021-03-01 17:57:47.184 project1[1695:130876] Closing2`\
354-`2021-03-01 17:57:47.188 project1[1695:130876] will become active`\
355-`2021-03-01 17:57:47.189 project1[1695:130876] did become active`\
356-`2021-03-01 17:57:47.190 project1[1695:130876] finalization`
351+```text
352+trev@macmini7 [/Users/trev/Programming/LAZARUS/laz_appnap_ex1] $ ./project1.app/Contents/MacOS/project1
353+2021-03-01 17:57:40.293 project1[1695:130876] initialization
354+2021-03-01 17:57:40.378 project1[1695:130876] will become active
355+2021-03-01 17:57:40.379 project1[1695:130876] did become active
356+2021-03-01 17:57:43.206 project1[1695:130876] will resign active
357+2021-03-01 17:57:43.207 project1[1695:130876] did resign active
358+2021-03-01 17:57:47.184 project1[1695:130876] Closing2
359+2021-03-01 17:57:47.188 project1[1695:130876] will become active
360+2021-03-01 17:57:47.189 project1[1695:130876] did become active
361+2021-03-01 17:57:47.190 project1[1695:130876] finalization
362+```
357363
358364Alternatively, you can view the output using the Applications \> Utilities \> Console.
359365
360366### Register for application-level occlusion notifications
361367
362368As soon as your application or any of its windows becomes hidden, your application should immediately halt any work that is no longer useful. When it becomes visible again, it can immediately refresh its content and resume any power-intensive operations.
363369
364370Your application is considered hidden from the user when:
365371
366372- Windows of other applications occlude the windows of your application.
367373- The screen saver is on and occluding all applications’ windows.
368374- Your application is in a Mission Control space where the user is not working.
369375
370376*applicationDidChangeOcclusionState* tells the delegate that the application’s occlusion state changed.
371377
372378On receiving this notification, you can query the application's occlusion state. Note that this only notifies about changes in the state of the occlusion, not when the occlusion region changes. You can use this notification to increase responsiveness and save power by halting any expensive calculations that the user cannot see.
373379
374380If the *NSApplicationOcclusionStateVisible* global constant variable is set, it means that at least part of a window owned by your application is visible, so your application should continue working as normal. If the variable is not set, it means that no part of any window owned by your application is visible and your application should reduce or halt its operations and calculations because the user will not be able to see the results.
375381
376382#### Example code
377383
378384In the example code below the relevant delegate method simply logs its occurrence. In a real application you would need to replace it with the desired behaviour to reduce or suspend relevant activities and restart activities as required.
379385
380386``` pascal
381387unit Unit1;
382388
383389{$mode objfpc}{$H+}
384390{$modeswitch objectivec1}
385391
386392interface
387393
388394uses
389395 Forms, StdCtrls, CocoaAll;
390396
391397type
392398
393399 { TForm1 }
394400
395401 TForm1 = class(TForm)
396402 Button1: TButton;
397403 procedure Button1Click(Sender: TObject);
398404 procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
399405 procedure FormCreate(Sender: TObject);
400406 private
401407
402408 public
403409
404410 end;
405411
406412 {TMyAppDelegate}
407413
408414 TMyAppDelegate = objcclass(NSObject)
409415 private
410416
411417 public
412418 procedure applicationDidChangeOcclusionState (aNotification: NSNotification);
413419 message 'applicationDidChangeOcclusionState:';
414420 end;
415421
416422var
417423 Form1: TForm1;
418424 myAppDelegate: TMyAppDelegate;
419425
420426implementation
421427
422428{$R *.lfm}
423429
424430{NSApplication Delegates}
425431
426432procedure TMyAppDelegate.applicationDidChangeOcclusionState(aNotification: NSNotification);
427433begin
428434 NSLog(NSStr('did change occlusion state'));
429435
430436 // if(NSApp.occlusionState AND NSApplicationOcclusionStateVisible) = 1 << 1 then // OR
431437 if (NSApp.occlusionState AND NSApplicationOcclusionStateVisible) = %00000010 then
432438 NSLog(NSStr('state: visible'))
433439 else
434440 NSLog(NSStr('state: invisible'))
435441end;
436442
437443{Button Events}
438444
439445procedure TForm1.Button1Click(Sender: TObject);
440446begin
441447 NSLog(NSStr('Closing'));
442448 Close;
443449end;
444450
445451{Form Events}
446452
447453procedure TForm1.FormCreate(Sender: TObject);
448454begin
449455 // NSApp
450456 // - The global variable for the shared application instance.
451457 // NSApplication.sharedApplication
452458 // - Returns the application instance, creating it if it doesn’t exist yet.
453459 NSApp := NSApplication.sharedApplication;
454460 NSApp.setDelegate(myAppDelegate);
455461end;
456462
457463procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
458464begin
459465 if(NSApp.isActive) then
460466 begin
461467 NSLog(NSStr('Exiting'));
462468 exit;
463469 end
464470 else
465471 // needed if any part of form is occluded or does not quit
466472 // until dock icon clicked when quitting via dock menu
467473 begin
468474 NSLog(NSStr('Closing2'));
469475 Application.BringToFront;
470476 CloseAction := TCloseAction.caFree;
471477 end;
472478end;
473479
474480Initialization
475481 NSLog(NSStr('initialization'));
476482 myAppDelegate := TMyAppDelegate.alloc.init;
477483
478484Finalization
479485 NSLog(NSStr('finalization'));
480486 myAppDelegate.Release;
481487end.
482488```
483489
484490Typical log output when the executable is started from an Applications \> Utilities \> Terminal:
485491
486-`trev@macmini7 [/Users/trev/Programming/LAZARUS/laz_appnap_ex2] $ ./project1.app/Contents/MacOS/project1`\
487-`2021-03-01 18:25:51.348 project1[1894:139611] initialization`\
488-`2021-03-01 18:25:51.427 project1[1894:139611] did change occlusion state`\
489-`2021-03-01 18:25:51.427 project1[1894:139611] state: visible`\
490-`2021-03-01 18:25:55.842 project1[1894:139611] did change occlusion state`\
491-`2021-03-01 18:25:55.842 project1[1894:139611] state: invisible`\
492-`2021-03-01 18:26:00.552 project1[1894:139611] Closing2`\
493-`2021-03-01 18:26:00.558 project1[1894:139611] finalization`
492+```text
493+trev@macmini7 [/Users/trev/Programming/LAZARUS/laz_appnap_ex2] $ ./project1.app/Contents/MacOS/project1
494+2021-03-01 18:25:51.348 project1[1894:139611] initialization
495+2021-03-01 18:25:51.427 project1[1894:139611] did change occlusion state
496+2021-03-01 18:25:51.427 project1[1894:139611] state: visible
497+2021-03-01 18:25:55.842 project1[1894:139611] did change occlusion state
498+2021-03-01 18:25:55.842 project1[1894:139611] state: invisible
499+2021-03-01 18:26:00.552 project1[1894:139611] Closing2
500+2021-03-01 18:26:00.558 project1[1894:139611] finalization
501+```
494502
495503Alternatively, you can view the output using the Applications \> Utilities \> Console.
496504
497505## See also
498506
499507- [[macOS Energy Efficiency Guide]]
500508- [[Accessing macOS System Information|NSProcessInfo]] - accessing system information.
501509
502510## External Links
503511
504512- [Apple: Energy Efficiency Guide for Mac Apps](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/index.html#//apple_ref/doc/uid/TP40013929-CH15-SW1).
505513- [Apple: NSActivityOptions](https://developer.apple.com/documentation/foundation/nsactivityoptions).
506514- [Apple: NSProcessInfo](https://developer.apple.com/documentation/foundation/nsprocessinfo#/apple_ref/occ/cl/NSProcessInfo).
507515- [Apple: NSApplicationDelegate](https://developer.apple.com/documentation/appkit/nsapplicationdelegate).
508516- [Apple: NSApplicationDidChangeOcclusionStateNotification](https://developer.apple.com/documentation/appkit/nsapplicationocclusionstate/nsapplicationocclusionstatevisible).
509517- [Apple: NSTimer](https://developer.apple.com/documentation/foundation/nstimer).
510518
511519---
512520*From the Free Pascal wiki, page "macOS App Nap", as it stood on 2021-03-01 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history