One of the commonest everyday mysteries of Macs is how they run apps.
If you’re content with the one word answer “Because”, when something goes wrong – an app crashes or quits immediately that you try to start it – you won’t know what to do next. Understanding how apps run also gives you insight into what you see in Activity Monitor, and how to use that and other tools to work out whether your Mac has sufficient memory, or adding another 8 GB might be worth your investment.
How apps run has also been changing steadily as maOS has evolved. A few years ago, security checks were minimal, there was no such thing as notarization, many apps weren’t even signed, and catalysts were used in industrial chemistry. Whether or not you think Catalina has advanced as a result of these changes, they’re here, and it’s important to know how to work with them.
Some apps are launched by macOS itself, but those which are most visible you launch by double-clicking the app icon, through the Dock, opening a file which is currently associated with that specific app, or similar.
When you go to open a document associated with an app which isn’t running, LaunchServices in macOS looks up which app is associated with that specific file or that document type, then tries to launch that app, sending it a message to open the document which you tried to open.
Inside the app
Apps aren’t just lumps of code, but contain set structures which you can browse by selecting the app in the Finder, and using the contextual menu to Show Package Contents. You will next see, at the top level of its folder structure, a single folder named Contents, within which are several other standard folders.
The folder _CodeSignature contains the code signature data used by Gatekeeper to validate the app and its integrity. The executable code, the program itself, is located in the folder named MacOS. The folder named Resources contains all manner of components most commonly supporting the interface of the app, stored in different types of files such as .storyboardc (storyboards), .nib (nibs), .icns (icons), various graphics files containing other items for display, and folders with language-specific localisation details such as text strings.
There may also be named folders for other code resources, in particular Frameworks and Libraries, and Plugins. Larger and more complex apps may have many such code resources, and they can also be placed in locations other than the .app itself.
Browsing the contents of an app can also tell you what class of app it is. If it has a folder named _MASReceipt, then it was supplied by the App Store. If it has a file named CodeResources, that’s the stapled notarization ticket. If you want to know if it’s a Catalyst app (a macOS version of an iPadOS app), then select the Info.plist file and scroll down its QuickLook preview to find its CFBundleIdentifier key. The next line in that file gives the app ID, which invariably starts with the term
maccatalyst if it’s a Catalyst app.
Get set and go
Before trying to run an app, macOS checks to see whether it has a quarantine flag set, because it has been downloaded from a remote source and its signature has yet to be checked by Gatekeeper. If the app has already cleared Gatekeeper’s scrutiny, or once it has passed that, the instruction to launch the app is passed on by LaunchServices. All apps (and command tools) in Catalina are now checked for known malware by XProtect whenever they’re opened. I have written a full account of Gatekeeper checks, and the circumstances in which they occur, in this article.
Some problems in an app will result in its immediate termination, just as if it had crashed. This can be caused by a broken signature, revoked signing certificate, damage to any of its executable code, or XProtect detecting a malware signature, and more, depending on whether the quarantine flag is set. If the flag isn’t set, and the app has been run from that location before, macOS is currently more tolerant of changes to the app or problems with its signature. The rules are complex, and explained in that article.
macOS responds to your instruction to launch an app by loading the code inside the MacOS folder and executing it. As part of that the dynamic linker shared library
dyld is loaded. These are put into the program’s address space (allocated memory). As most programs call on various code libraries, the dynamic linker loads those libraries which the program needs. Inside the app’s process, the main thread of the app is created, and that is used to begin executing the code from within memory. The app’s code then takes over, and the app is now running.
Full details of this elaborate process are given in Jonathan Levin’s book *OS Internals, volume I.
During launching, macOS’s virtual memory manager creates a virtual address space (allocated virtual memory) for each process, and divides each up into equal chunks known as pages. These are mapped to hardware addresses in physical memory (RAM) in a page table. Whenever an app’s code needs to access memory, these page tables are used to look up the real address in RAM.
So the running program sees an almost limitless area of memory, which the memory management hardware and software then converts into real, physical memory.
Because this huge virtual memory cannot all fit into physical memory, there will be times when a running program tries to access some memory which is not currently held in real, physical memory: that is known as a page fault, and causes the code to stop running whilst the memory manager loads the data from disk into an available page in physical memory. This is known as paging in, and once complete the code is allowed to carry on running.
If there are no free pages in real, physical memory, an existing page must be released to create a free page; this normally requires the data currently held in that page to be written out into the backing store on disk, a process known as paging out (or swapping out).
Paging in and out stop the program from running, and thus slow the app down. If a lot of page faults occur and there are no free pages, this results in what used to be called on hard drives disk thrashing, which is slow and has a major performance hit.
The macOS kernel maintains system-wide lists of real, physical memory, and uses thresholds to manage pages as efficiently as possible, to minimise paging in and out. This ensures that pages that contain data that has not been used recently may be paged out to provide free pages when needed, for example. The memory manager works hard to ensure optimal memory use, but works best when there are plenty of free pages.
The app itself
Apps have a single main entry point, called when they are launched, and most use this main function very little. All it normally does is give control to the AppKit framework, which then takes over.
The AppKit framework creates the application object itself, loads the storyboard/nib file specified in the app’s Info.plist file, and sets that up for the app. The app itself then takes control, initialising all that it needs, including setting up the app menu bar, opening any files (such as when you launch the app by double-clicking an associated document), and tidies up.
The app then sits in its Main Event Loop, an endless code loop which responds to your actions. For instance, if you then click on the menu bar, the menu is displayed, and the app waits for you to select a menu item. Once you do, the app is informed as to which item you selected, and the Main Event Loop processes that selection by choosing the code which it needs to execute – this is the process of event handling, which invokes all the different code functions built into the app.
There are two main types of user event which are passed to the app: key and mouse/trackpad events. Key events are sent to the window in which the entry point is currently set, and mouse/trackpad events to the window in which the event occurred. So if you click on a button, the window containing that button is told of your action, so it can respond appropriately.
App Store apps are required to run in a sandbox, something that other apps can choose to do if they wish. The sandbox then prevents them from doing anything outside its limits unless the app has the entitlement to do so. I have explained in broad terms how the entitlement system works in this article.
Mojave introduced an elaborate system of privacy protection, which has been expanded considerably in Catalina. This prevents apps from accessing certain locations and resources which Apple deems sensitive. To gain access to them, the app may have to indicate its intention, contain a section of text giving its reasons for doing so (for display to the user), and the user must then give their consent when prompted to do so. Although many apps run without encountering any issues with these new protections, they can cause others serious problems. I have written about some of the issues here, and (when I have steeled myself to tackle this thorny area again) will do so in the future for Catalina’s changes.
One important concept in privacy protection is the Attribution Chain, detailed here. This arises because gaining access to protected resources almost invariably requires user consent. When a GUI app wants to access the built-in camera, for example, macOS will prompt you to give your consent before adding that app to the Camera list in the Privacy tab of the Security & Privacy pane. Command tools, services and other executable code which lacks a GUI cannot do this, so macOS builds a link to a GUI component which can host the consent process: that is known as the Attribution Chain, and is set up for processes by the TCC subsystem.
Catalina introduces a new subsystem named RunningBoard, which has the ability to control apps’ lifecycles and usage of resources such as memory, CPU and GPU. At present, this only monitors the great majority of apps, and doesn’t manage or intervene. The exceptions to this are Catalyst apps, another introduction for Catalina.
Catalyst apps run in a unique environment, in which they rely on another app, UIKitSystem, to interface between their iPadOS world and macOS. In addition, RunningBoard manages them, as it does in iPadOS. For example, if your Mac runs short of physical memory and a Catalyst app is idling in the background, macOS can choose to exit the app in order to free up its memory. This happens quite commonly in iOS and iPadOS, where devices have much smaller amounts of physical memory, so often come under memory pressure.
Macs generally have significantly more physical memory and (unlike iOS/iPadOS devices) use swap space to keep physical memory free. It remains to be seen whether these new controls are beneficial in macOS. So far, user experience with some Catalyst apps suggests that they are more profligate in their use of memory, and cause rather than solve memory pressure problems in macOS.
Many apps use iCloud features. When those apps start up, they have phases of intense iCloud connection activity, during which they initialise their iCloud connections, enabling you to use their features. This is a large and essentially undocumented topic, some aspects of which are mentioned in this article, and covered in the documents for my free utility Cirrus, which includes a special iCloud log browser.
Clues and more
Some of the above steps are recorded in the logs, giving you clues as to what is going on. Sometimes these entries are informative, telling you that certain code libraries have been loaded, or they may warn you of errors, which could cause problems with the app, perhaps even resulting in it suddenly quitting.
Understanding the normally hidden steps in the process of launching an app helps you discover what has gone wrong, and devise strategies for trying to deal with it.
Understanding how macOS manages memory enables you to work out whether your Mac has enough physical memory, and how you can improve performance.
There is further information and detail in the documentation which accompanies Apple’s huge but free development kit, Xcode, available in the App Store, and in Jonathan Levin’s book *OS Internals, volume I.
This article has covered a great deal of ground, much of it incredibly complex. If you spot any errors, please don’t hesitate to correct me, so that I can correct the text.