How macOS launches an iOS app

In my previous explorations of how M1 Macs run iOS and iPadOS apps, I discovered that they are always run in translocation, and that they engage in elaborate relationships with RunningBoard and other new sub-systems in macOS. This article dives considerably deeper, looking in detail at how macOS launches an example iOS app. I provide short snippets from the log to support each step, with times being given in decimal seconds.

As this iOS app is being launched in the Finder, the double-click is marked by two pairs of activities recording that
1.033965 sendAction:
giving the start time for these events at just after 1 second.

LaunchServices, which handles Finder interaction such as the launching of apps, recognises that this is an iOS app, and needs to be launched using CoreServicesUIAgent. Next, a mobile service named MIS (com.apple.mis) checks the app bundle against a blacklist, and validates it, which System Security Policy declares as forming a valid App Wrapper.
1.054198 com.apple.syspolicy MIS validation result: 0
1.054230 com.apple.syspolicy appWrapperPolicyResult: <private>, AWPolicyResult: 1,1,0

LaunchServices prepares to translocate the app from its current location to a hidden folder, in which it’s wrapped with a couple of Property Lists and run. All iOS/iPadOS apps run in macOS are translocated. This isn’t for security purposes (as is the case when first running macOS apps with a quarantine flag set), but to work around two limitations:

  • iOS/iPadOS apps may expect to be run from a path which doesn’t contain whitespaces. The path to the translocation folder guarantees that.
  • In macOS, the user can run apps from (almost) any path, such as the Desktop, and can rename apps. The translocation path ensures the app’s name and path remain fixed.

Wrapped iOS/iPadOS apps aren’t retained indefinitely, but periodically cleaned up.
1.055056 com.apple.launchservices LAUNCH: translocate to <private> from <private>

RunningBoard

LaunchServices announces the app will be launched through RunningBoard, and sets the flag to disable pointer authentication for that launch. RunningBoard then registers the launch request, executes it, and starts tracking the process.
1.060032 com.apple.runningboard Creating and launching job for: application<com.parrot.freeflight6(501)>
1.072092 com.apple.runningboard Now tracking process: [application<com.parrot.freeflight6(501)>:9863]

From here on, a long series of assertions are handled by RunningBoard, to change its management of the app as launch progresses. Initial settings include:

  • Set jetsam priority to 0 [0] flag[1]
  • running-active (role: Background)
  • Set darwin role to: Background
  • Set GPU priority to “deny”
  • Reset CPU monitoring limits to defaults

and processStartTime is recorded.

Sandbox and TCC

LaunchServices sends the Open App initial AppleEvent to the app, and the ProcessManager (com.apple.processmanager) starts the app’s stopped process. Following that, the app’s frameworks and any other components are scanned for malware, according to iOS Platform Policy. The App Sandbox is created and provided to the app.
1.518085 com.apple.containermanager <private> is sandboxed but container (type: 2) is not data separated. NOT issuing token for path: [<private>]
1.518921 FreeFlight6[9863]: AppSandbox request successful

After checking in with the ProcessManager
1.536299 com.apple.processmanager CHECKIN: pid=9863
the app then negotiates with TCC to gain access to the resources it requires.

FuseBoard

The next of the new -board sub-systems which it works with is FuseBoard
1.577026 com.apple.FuseBoard Now tracking client application: <private>
ProcessManager puts the app to the front, which in turn causes LaunchServices to put the app to the front of its visible list. AppKit (com.apple.AppKit) informs the app of the current system appearance
1.581368 com.apple.AppKit Current system appearance, (HLTB: 1), (SLS: 0)

FrontBoard

FrontBoard registers the app process
1.587628 com.apple.FrontBoard Registering process for handle: [application<com.parrot.freeflight6(501)>:9863]
and registers an event dispatcher and workspace (FBSWorkspace) for the app.

At first, FrontBoard ‘bootstraps’ the app into the background, starts a WatchDog for it, and monitors its resource allowance. The UIKit helper sub-system (com.apple.UIKit.MacHelper) is involved with this too. FuseBoard and FrontBoard connect up, and FrontBoard makes a policy for creating scenes for the app. At this stage, FrontBoard conveniently lists the processes and scenes it’s currently managing.

In addition to this iOS app, which is managed by FrontBoard as an FBApplicationProcess, and UIKitSystem app which is an FBProcess, regular processes managed by FrontBoard include:

  • com.apple.notificationcenterui.agent, a daemon as FBProcess,
  • com.apple.weather.widget, an XPC service as FBExtensionProcess,
  • com.apple.mobiletimer.WorldClockWidget, an XPC service as FBExtensionProcess,
  • com.apple.ScreenTimeWidgetApplication.ScreenTimeWidgetExtension, an XPC service as FBExtensionProcess,
  • com.apple.stocks.widget, an XPC service as FBExtensionProcess,
  • com.apple.news.widget, an XPC service as FBExtensionProcess.

Running the app

At this stage, CFPrefsD loads system and user preferences for the app, and it finally starts running its own code. This leads to the creation of windows, which are added to FuseBoard’s list of scenes. AppKit and UIKit helpers join in
1.640665 com.apple.AppKit Enabling UIKit event handling compatibility mode.
1.664302 com.apple.UIKit.MacHelper applicationDidFinishLaunching (macOS)
1.664310 com.apple.UIKit.MacHelper Checking AppleEvent for background launch data
1.664538 com.apple.UIKit.MacHelper Creating foregrounded scene (8758226F-47D0-4C13-822A-495316FB61FF)
1.664539 com.apple.UIKit.MacHelper Tracking scene creation. 8758226F-47D0-4C13-822A-495316FB61FF

The app then asks FuseBoard for a new scene
1.665047 Default 80491 630 com.apple.FuseBoard [application<com.parrot.freeflight6(501)>:9863][0xb8ce] Received scene request...
1.665799 Default 5332 630 com.apple.FuseBoard [application<om.parrot.freeflight6(501)>:9863][0xb8ce] Request complete! Created scene application<com.parrot.freeflight6(501)>:9863-default (persistenceID 8758226F-47D0-4C13-822A-495316FB61FF)

Final steps in the launch sequence include the creation of the menu bar as a FrontBoard scene together with UIKit MacHelper
1.827502 Default 80491 630 com.apple.FrontBoard [NSMenuBarScene_C28AB60E-6B34-4E98-8AA5-38AD86A35ED6] Scene action [Logical Activate][0x3ae2] completed with success: 1

After about 0.8 seconds from the double-click to launch the iOS app, it’s ready and waiting for the user.

Launch sequence

Here’s a short summary of the launch sequence:

  1. LaunchServices recognises the iOS/iPadOS app, and its need for launching with CoreServicesUIAgent.
  2. MIS checks the wrapper, and it’s approved under security policy.
  3. LaunchServices translocates the app into a wrapper in a path without whitespace.
  4. LaunchServices launches the app through RunningBoard, and pointer authentication is disabled.
  5. RunningBoard starts tracking and managing the app.
  6. The app’s frameworks etc. are scanned for malware.
  7. The app’s sandbox is created.
  8. The app negotiates access to resources with TCC.
  9. The app registers with FuseBoard and FrontBoard, which start managing it.
  10. FrontBoard creates a WatchDog for the app and monitors its use of resources.
  11. The app loads its preferences from CFPrefsD.
  12. FuseBoard and FrontBoard handle the app’s scenes, including its menu bar.
  13. The app’s code runs.