How to Boot a Virtual iPhone Using Virtualization.framework with vphone-cli

vphone-cli boots a virtual iPhone by constructing a PV=3 hardware model, configuring a VZVirtualMachine with kernel and ramdisk assets, and orchestrating the complete lifecycle through Apple's Virtualization.framework using pure Swift.

The open-source tool vphone-cli demonstrates how to leverage Apple's Virtualization.framework to run iOS firmware in a virtualized environment on macOS. This article examines the precise sequence of steps the CLI tool follows to boot a virtual iPhone, from parsing command-line arguments to establishing host-guest communication channels.

Parsing CLI Arguments and Boot Options

The boot process begins in VPhoneCLI.swift and related command-line handlers such as VPhoneVMLaunchCLI.swift. These components parse user-specified options including the firmware variant, RAM allocation, and display settings.

The parsed configuration is stored in a VPhoneVirtualMachine.Options structure, which serves as the single source of truth for the VM's hardware and boot parameters. This options object propagates through the initialization chain to configure the virtual hardware accurately.

Constructing the Hardware Model

Before the Virtualization.framework can instantiate a VM, it requires a valid hardware model descriptor. The file VPhoneHardwareModel.swift generates a PV=3 hardware model using the Dynamic library to access private API surfaces.

This model supplies essential device descriptors for the CPU, GPU, and other virtualized components required by the framework. Without this specific hardware model construction, the Virtualization.framework would reject the configuration when attempting to boot iOS firmware.

Configuring the Virtual Machine

The core configuration logic resides in VPhoneVirtualMachine.swift. Here, the tool creates a VZVirtualMachineConfiguration and populates it with critical parameters:

  • configuration.hardwareModel is set to the PV=3 model constructed earlier
  • Boot resources include the kernel and ramdisk derived from the selected firmware files
  • Graphics configuration uses VZMacGraphicsDeviceConfiguration to enable high-resolution display and touch input forwarding
  • Network communication is established via a vsock listener on port 1337 for host-guest communication

This configuration object encapsulates the complete virtual hardware environment before instantiation.

Instantiating and Starting the VM

Once configured, VPhoneVirtualMachine.swift creates the actual VZVirtualMachine instance. Because UI-related actions require the main thread, this creation occurs on the @MainActor.

The boot() method initiates execution by calling virtualMachine.start(completionHandler:). The completion handler logs success or failure, and upon successful boot, initializes the vsock client (VPhoneControl.swift) to communicate with the guest daemon.

import VPhoneCLI

let options = VPhoneVirtualMachine.Options(
    firmwareVariant: .regular,
    ramSizeGB: 4,
    displayScale: 2.0
)

let vm = try VPhoneVirtualMachine(options: options)
try vm.boot()  // Starts the VZVirtualMachine

Rendering the Display and Handling Input

User interaction requires two specialized components. VPhoneVirtualMachineView.swift provides a subclass of VZVirtualMachineView that renders the VM's framebuffer to the screen.

VPhoneWindowController.swift embeds this view within an NSWindow and attaches VPhoneKeyHelper to forward host keyboard events and hardware-key signals into the virtual iPhone. This creates a seamless bridge between physical input devices and the virtualized iOS environment.

Communicating with the Guest Daemon

After the VM boots, the host-side VPhoneControl class opens a vsock connection to the guest daemon (vphoned). This daemon launches automatically via the VM's init scripts and exposes a control interface for advanced operations.

Through this channel, vphone-cli issues commands for IPA installation, screen recording, and location synchronization. The vsocket connection on port 1337 serves as the primary command-and-control interface throughout the VM's lifecycle.

let control = VPhoneControl()
control.connect { result in
    switch result {
    case .success:
        control.send(json: ["command": "ping"])
    case .failure(let error):
        print("Failed to connect:", error)
    }
}

Lifecycle Management and Cleanup

The VPhoneAppDelegate.swift file manages the application's main run-loop and monitors for termination signals. When the user sends SIGINT or closes the window, the delegate invokes virtualMachine.stop() to gracefully shut down the guest.

This cleanup sequence ensures proper resource deallocation and prevents corruption of the virtual disk images. The tool exits cleanly only after the Virtualization.framework confirms the VM has terminated.

To build and run the tool from source:


# Build the signed binary (required entitlements)

make build

# Boot the VM with the default (regular) firmware

./.build/release/vphone-cli boot

Summary

  • vphone-cli uses VPhoneHardwareModel.swift to generate a PV=3 hardware model required by Virtualization.framework for iOS boot compatibility.
  • The VPhoneVirtualMachine.swift file constructs a complete VZVirtualMachineConfiguration including graphics, storage, and vsock networking on port 1337.
  • VM instantiation occurs on @MainActor due to UI requirements, with the boot() method calling start(completionHandler:) to begin execution.
  • Input handling combines VPhoneVirtualMachineView.swift for framebuffer rendering and VPhoneKeyHelper for keyboard event forwarding.
  • Host-guest communication relies on VPhoneControl.swift connecting to the vphoned daemon via vsock for runtime management commands.

Frequently Asked Questions

What hardware model does vphone-cli use for virtual iPhone compatibility?

vphone-cli generates a PV=3 hardware model through VPhoneHardwareModel.swift using the Dynamic library. This private-API-friendly approach provides the specific CPU, GPU, and device descriptors that Virtualization.framework requires to boot iOS firmware rather than macOS.

Why must the VZVirtualMachine be created on @MainActor?

The VZVirtualMachine instance requires the main thread because it interacts with UI components such as VZVirtualMachineView and window management systems. VPhoneVirtualMachine.swift ensures all initialization and boot sequences run on @MainActor to satisfy these framework constraints.

How does the host communicate with the virtual iPhone after boot?

Once running, the host opens a vsock connection on port 1337 via VPhoneControl.swift to reach the guest vphoned daemon. This daemon, launched by the VM's init scripts, handles commands for installing IPAs, recording the screen, and synchronizing location data between host and guest.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →