Skip to main content

FBSimulatorControl

FBSimulatorControl is the macOS Framework that implements all functionality associated with iOS Simulators within idb. It can be used independently of idb as it is a standalone Framework.

CoreSimulator.framework

CoreSimulator is the Private Framework that is the interface for most Simulator related functionality on macOS. In previous Xcode versions, CoreSimulator was bundled inside of Xcode, but it is now installed at the System level just like MobileDevice.framework. It may be upgraded to a newer version as part of the install process of Xcode itself.

CoreSimulator is used by Xcode and simctl as the Framework used to manipulate Simulators. It has Objective-C classes representing a set of Simulators within a directory (SimDeviceSet wrapped by FBSimulatorSet) and an individual iOS Simulator (SimDevice, wrapped by FBSimulator). There is also a Class that behaves a lot like an "entrypoint" to the Framework in SimServiceContext, this performs initialization of external services and is aware of the various configurations of Simulators that are availabile.

simctl

simctl is essentially a CLI that exposes iOS Simulator functionality by linking and using CoreSimulator. This binary is bundled inside of Xcode, and typically addressed via usage of the xcrun command. xcrun is essentially a trampoline that addresses binaries that are bundled within Xcode by using the value defined in xcode-select

The overwhelming majority of Simulator functionality is not implemented in simctl, it is implemented within CoreSimulator with simctl providing an accessible way of using this functionality. Having this behaviour implemented at the Framework level so that Simulator.app and simctl behave identically when using their user interfaces.

CoreSimulatorService

CoreSimulatorService is a user-level daemon that is bootstrapped by any usage of SimServiceContext, effectively any usage of iOS Simulators will cause this service to be created and launched. This is an XPC service contained within the CoreSimulator.framework bundle. This service is responsible for starting and managing Simulators.

When using CoreSimulator as a client Framework it will transparently communicate with CoreSimulatorService. The overwhelming majority of CoreSimulator APIs that do meaningful work are essentially performing IO to CoreSimulatorService, though the asynchronous nature of this work isn't completely consistent. Some CoreSimulator APIs (for instance those associated with launching an iOS Application) do have asynchronous methods, but others (such as the instantiation of a SimServiceContext) do not. CoreSimulatorService still gets much of it's implementation from CoreSimulator.framework, touching different areas of the API.

Having the "work" of iOS Simulators performed within the context of share user daemon is likely due to the needs to synchronize and consolidate state. The service is also an effective caching mechanism for runtime and device profiles. There are a few downsides to this approach. Firstly, CoreSimulatorService is effectively a single point of failure. If CoreSimulatorService becomes stuck, or a client of CoreSimulator exhibits pathological behaviour, then all iOS Simulator functionality on a given host will fail. iOS Simulator functionality will effectively halt until CoreSimulatorService restarts, either by the hung CoreSimulatorService terminating and restarting or via reboot.

Secondly, the lifecycle of CoreSimulatorService is tied to that of the selected Xcode. This means that different versions of Xcode cannot be used concurrently on the same host; CoreSimulatorService can only be aware of a single Xcode at any point in time. Switching Xcodes and fetching a new CoreSimulatorService (for instance via a simctl command) will cause CoreSimulatorService to restart, disconnecting existing clients and killing booted Simulators.

SimRuntime

An iOS Simulator Runtime is all of the required components for running an iOS Simulator of a given iOS version. This is a bundle, where the contents closely match the makeup of the files on disk on a physical device. This includes binaries that are compiled for the host architecture (x86_64 in the case of Intel Macs, ARM64 in the case of ARM based Macs) as well as Frameworks. The Frameworks within a SimRuntime match those of iOS, instead of those of the macOS host. There are often subtle differences in the iOS and macOS APIs, even within the same Framework. A single SimRuntime represents a single iOS version.

Each version of Xcode is bundled with SimRuntimes for the most recent iOS version that is relevant for the Xcode version across iOS, tvOS and watchOS. However, additional iOS Versions can be supported on a given version of Xcode via the "Components" section within Xcode.app. These bundles are then installed into /Library/Developer/CoreSimulator/Profiles/Runtimes on the host system. Runtime bundles are backwards, but not forwards compatible. For example, Xcode 11 has support for iOS 13 (the latest iOS version associated with this Xcode version) and earlier versions of iOS, but not for iOS 14.

launchd_sim

iOS, like macOS has launchd as it's "root process" (often PID 1). However, iOS Simulators have their own version of launchd as a root process. This launchd_sim is effectively the "root process" of the iOS Simulator runtime, but not of the macOS host. This launchd_sim is required by the Simulator OS in order to launch Applications, manage services etc. Each launched iOS Simulator has it's own launchd_sim process, launched from the launchd_sim within the SimRuntime. This also means that processes within this nested launchd will only see the processes of the iOS Simulator, rather than all of those of the entire host (including other iOS Simulators running on the same host).

This launchd_sim can be interrogated the same as the launchd of the host, provided that the launchctl called is spawned within the launchd_sim of the iOS Simulator.

Device Sets

A Device Set is essentially a directory that contains a number of created iOS Simulators. The "Default Device Set" is located at ~/Library/Developer/CoreSimulator/Devices, this is the device set that is used by Xcode.app.

Custom device sets can be placed at any location on disk. This is useful for isolating the filesystems of created iOS Simulators from each other. For instance, if there are independent processes managing iOS Simulators on the same host it can be worthwhile having each of these processes manage their own device sets to prevent data races.

There is also an XCTestDevices directory at ~/Library/Developer/XCTestDevices. This is the set of Simulators that are used by xcodebuild, distinct from the user interface. This means that xcodebuild can manage and use it's own set of iOS Simulators, independent of the Xcode UI. This may exist for a similar reasons to why custom device sets are practical for automation scenarios. It would also be a confusing user experience if an iOS Simulator that was being used within xcodebuild was using an iOS Simulator that a user was using via Xcode when running UI Tests.

Simulator.app

This is the "Simulator" Application with which most developers will be familiar with. This Application effectively mirrors the state of launched iOS Simulators within CoreSimulatorService. It is not an essential part of booting and managing iOS Simulators; iOS Simulators can be booted and used without a Simulator.app launched for it. This makes using Simulators more practical in automation scenarios where a running macOS Application representing the iOS Simulator is not important or even desirable.

The Simulator Application will default to showing all iOS Simulators that are within the "Default Device Set". This means that booted iOS Simulators within "Custom Device Sets" will not be displayed within Simulator.app.

The functionality within this Application is largely implemented within CoreSimulator.framework and SimulatorKit.framework, with the UI implemented directly within the application itself.

Framebuffers via IOSurface

The screen from an iOS Simulator is rendered, regardless of whether there is an iOS Simulator application that is presenting this within a IO. An iOS Simulator can be launched independently of Simulator.app, since Simulators are kept alive by CoreSimulatorService.

In order for other Applications (mainly Simulator.app, but also for video recording within simctl) to get the iOS Simulator's Framebuffer for rendering, CoreSimulator can access the IOSurface of the screen of an iOS Simulator. A Simulator can have many screens, for instance when Simulating CarPlay and the main screen at the same time.

An IOSurface is an object that wraps a Framebuffer, with the contents of the Framebuffer being located within GPU memory. This IOSurface can be read and inspected across process boundaries. The iOS Simulator uses this IOSurface as the backing Framebuffer for it's view of an iOS Simulator.

IOSurface objects are also easily convertable to "Pixel Buffer" types that are used in video encoding. This allows FBSimulatorControl to implement video encoding of an iOS Simulator's Framebuffer in a way that avoids large copies of bitmap framebuffers on a per-frame basis.

IndigoHID

"Indigo" is a service present in the iOS Simulator that is used inside Simulator.app to synthesize "Input Events" that are understood within the iOS Simulator. This service is how clicking on the UI of the Simulator.app translate into touches within the iOS Simulator.

This uses "mach" IPC, where data structures are sent over a channel using mach_msg_send. These data structures are defined through the "Mach Interface Generator", which get compiled out of the Simulator.app binary. As such, FBSimulatorControl's understanding of the layout and values in these data structures are understood through reverse engineering.

The reverse engineering of this protocol, allows FBSimulatorControl to expose APIs that allow sending of touch events directly to the iOS Simulator without using Accessibility APIs in a UI Test. The combination of video streams and APIs for sending input events allows for the building of applications that expose a remote iOS Simulator.

SimulatorKit.framework

This is another macOS Framework that is used in iOS Simulator management. This Framework is not installed to the System, it is bundled within Xcode. Instead, this Framework is more used to implement functionality within Simulator.app.

For example, this Framework contains some of the Indigo client functionality for sending input events.