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 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 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 is essentially a trampoline that addresses binaries that are bundled within Xcode by using the value defined in
The overwhelming majority of Simulator functionality is not implemented in
simctl, it is implemented within
simctl providing an accessible way of using this functionality. Having this behaviour implemented at the Framework level so that
simctl behave identically when using their user interfaces.
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.
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.
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.
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).
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.
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
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.
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
The functionality within this Application is largely implemented within
SimulatorKit.framework, with the UI implemented directly within the application itself.
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
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.
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.
"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.
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
For example, this Framework contains some of the
Indigo client functionality for sending input events.