How to Use Virtualization.framework with Swift on Apple Silicon: A Complete Guide
Virtualization.framework on Apple Silicon lets you create macOS and Linux VMs in Swift by configuring a VZVirtualMachine, validating it, and calling start() with platform-specific options.
The Lakr233/vphone-cli repository provides a production-ready reference implementation. It demonstrates every step from hardware model creation to UI integration using pure Swift and Apple's native virtualization APIs. This guide walks through the same architecture used to run a virtual iPhone on Apple Silicon Macs.
Prerequisites and Platform Requirements
Virtualization.framework is Apple Silicon only. The VZMac* classes used throughout this guide are unavailable on Intel Macs.
Your project must target:
- macOS 15 or later (Sequoia)
- Apple Silicon CPU (M1/M2/M3/M4 series)
- Swift 6 and Xcode 15+
The vphone-cli source enforces these requirements at runtime by checking ProcessInfo.processInfo.isOperatingSystemAtLeast and verifying the hardware model identifier (PV = 3) exists.
Step 1: Import the Framework
Every file interacting with the VM begins with the framework import. In VPhoneVirtualMachine.swift, this single line enables the entire API surface:
import Virtualization
No bridging header or C++ interop is required. Virtualization.framework is a first-class Swift framework on macOS 15.
Step 2: Create the Hardware Model
Apple Silicon VMs require a VZMacHardwareModel that defines the virtual platform version. The vphone-cli project uses PV = 3, which corresponds to the virtual iPhone hardware:
// VPhoneVirtualMachine.swift:49-52
let hardwareModel = try VPhoneHardware.createModel()
A hardware model is immutable once created. You persist it alongside your VM and reuse it for subsequent launches. Changing hardware models mid-lifecycle invalidates the VM state.
Step 3: Manage Machine Identity and NVRAM
Every macOS VM needs a stable VZMacMachineIdentifier and auxiliary NVRAM storage. The identifier must persist across reboots; NVRAM stores boot arguments and firmware variables.
In VPhoneVirtualMachine.swift:55-84, the code either loads an existing identifier from a manifest or generates a fresh one:
let machineIdentifier = VZMacMachineIdentifier()
// Create auxiliary storage for NVRAM variables
let auxStorage = try VZMacAuxiliaryStorage(
creatingStorageAt: storageURL,
hardwareModel: hardwareModel,
options: .allowOverwrite
)
The project then writes boot arguments to enable serial console output (VPhoneVirtualMachine.swift:36-50):
let bootArgs = "serial=3 debug=0x104c04"
// Private API usage in vphone-cli; production code uses setData:forKey:
Dynamic(auxStorage)._setDataValue(
bootArgs.data(using: .utf8)!,
forNVRAMVariableNamed: "boot-args"
)
Step 4: Configure the macOS Platform
The VZMacPlatformConfiguration ties together hardware model, machine identifier, and NVRAM. This is specific to Apple Silicon VMs—Linux VMs use VZGenericPlatformConfiguration instead.
From VPhoneVirtualMachine.swift:106-135:
let platform = VZMacPlatformConfiguration()
platform.hardwareModel = hardwareModel
platform.machineIdentifier = machineIdentifier
platform.auxiliaryStorage = auxStorage
Platform configuration is validated automatically when you build the final VM configuration. Mismatched hardware models and identifiers produce clear error messages at validation time.
Step 5: Build the Complete VM Configuration
VZVirtualMachineConfiguration aggregates all device and resource settings. The vphone-cli implementation (VPhoneVirtualMachine.swift:152-224) demonstrates a comprehensive setup:
CPU and Memory
let config = VZVirtualMachineConfiguration()
// Enforce minimums automatically
config.cpuCount = max(4, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
config.memorySize = max(4 * 1024 * 1024 * 1024,
VZVirtualMachineConfiguration.minimumAllowedMemorySize)
Graphics Display
let graphics = VZMacGraphicsDeviceConfiguration()
graphics.displays = [
VZMacGraphicsDisplayConfiguration(
widthInPixels: 1290,
heightInPixels: 2796,
pixelsPerInch: 460 // iPhone Retina density
)
]
config.graphicsDevices = [graphics]
Audio Devices
let sound = VZVirtioSoundDeviceConfiguration()
sound.streams = [
VZVirtioSoundDeviceInputStreamConfiguration(), // host mic → guest
VZVirtioSoundDeviceOutputStreamConfiguration() // guest audio → host speakers
]
config.audioDevices = [sound]
Storage
let diskAttachment = try VZDiskImageStorageDeviceAttachment(
url: diskURL,
readOnly: false
)
let disk = VZVirtioBlockDeviceConfiguration(attachment: diskAttachment)
config.storageDevices = [disk]
Serial Port (PL011 UART)
The virtual iPhone uses a PL011 serial controller for console access. vphone-cli accesses this through private API wrappers:
// Private API: _VZPL011SerialPortConfiguration
let serial = Dynamic._VZPL011SerialPortConfiguration().asObject as! VZSerialPortConfiguration
let pipeIn = Pipe()
let pipeOut = Pipe()
serial.attachment = VZFileHandleSerialPortAttachment(
fileHandleForReading: pipeIn.fileHandleForReading,
fileHandleForWriting: pipeOut.fileHandleForWriting
)
config.serialPorts = [serial]
Optional: VSocket for Host-Guest Communication
config.socketDevices = [VZVirtioSocketDeviceConfiguration()]
This creates a vsock channel used by vphone-cli's control daemon (vphoned) to exchange JSON commands between host and guest.
Step 6: Validate and Instantiate the VM
Configuration errors surface during validation, before any resources are allocated. From VPhoneVirtualMachine.swift:291-296:
try config.validate()
let virtualMachine = VZVirtualMachine(configuration: config)
validate() throws VZError with specific codes for unsupported configurations—helpful for debugging memory size mismatches or unavailable device types.
Step 7: Start the VM with Options
Apple Silicon VMs support platform-specific start options. The vphone-cli project implements DFU mode forcing for firmware development:
let startOptions = VZMacOSVirtualMachineStartOptions()
// Private API for DFU mode; standard API available for normal boot
Dynamic(startOptions)._setForceDFU(true)
try await virtualMachine.start(options: startOptions)
The start(options:) method is asynchronous and throws on immediate failures. Guest crashes or panics are reported through the delegate instead.
Step 8: Handle VM Lifecycle Events
Assign a VZVirtualMachineDelegate to respond to guest state changes. VPhoneVirtualMachine.swift:81-97 adopts this protocol:
extension VPhoneVirtualMachine: VZVirtualMachineDelegate {
func guestDidStop(_ virtualMachine: VZVirtualMachine) {
// Clean up resources, notify UI
}
func virtualMachine(_ vm: VZVirtualMachine, didStopWithError error: Error) {
// Log crash details, offer restart
}
}
Delegate methods execute on the VM's internal queue. Dispatch to the main queue for UI updates.
Step 9: Integrate with AppKit UI
Display the VM in a native window using VZVirtualMachineView. The VPhoneAppDelegate.swift file orchestrates this:
let vmView = VZVirtualMachineView()
vmView.virtualMachine = virtualMachine
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 720, height: 1440),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.contentView = vmView
window.makeKeyAndOrderFront(nil)
VZVirtualMachineView handles input forwarding, display scaling, and cursor capture automatically. Subclass it to intercept keyboard events for special key combinations, as VPhoneVirtualMachineView.swift demonstrates.
Advanced: Synthetic Devices and Debugging
The vphone-cli source includes several sophisticated configurations worth studying:
| Feature | Implementation | Source Location |
|---|---|---|
| Synthetic battery | VZMacSyntheticBatterySource |
VPhoneVirtualMachine.swift:47-60 |
| GDB debug stub | VZGDBDebugStubConfiguration |
VPhoneVirtualMachine.swift:47-60 |
| USB touch screen | VZUSBTouchScreenConfiguration |
VPhoneVirtualMachine.swift:47-60 |
These are optional but demonstrate the full capability of Virtualization.framework on Apple Silicon.
Summary
- Import
Virtualization.frameworkand target macOS 15+ on Apple Silicon only - Create
VZMacHardwareModelwith your platform version, then persist it - Generate
VZMacMachineIdentifierandVZMacAuxiliaryStoragefor NVRAM - Assemble
VZMacPlatformConfigurationlinking all platform components - Build
VZVirtualMachineConfigurationwith CPU, memory, graphics, storage, audio, serial, and network devices - Validate with
config.validate()before instantiatingVZVirtualMachine - Start asynchronously using
VZMacOSVirtualMachineStartOptionsfor platform-specific behavior - Implement
VZVirtualMachineDelegateto respond to guest lifecycle events - Display with
VZVirtualMachineViewin anNSWindowfor interactive use
The Lakr233/vphone-cli repository provides working implementations of every step above, making it the definitive reference for Virtualization.framework with Swift on Apple Silicon.
Frequently Asked Questions
What makes Virtualization.framework Apple Silicon only?
The VZMacHardwareModel, VZMacPlatformConfiguration, and related classes implement Apple's proprietary ARM virtualization extensions. These leverage the Apple Silicon CPU's EL2 hypervisor mode and memory architecture. Intel Macs expose different virtualization capabilities through Hypervisor.framework instead.
Can I run Linux VMs with the same code?
Yes, with modifications. Linux guests use VZLinuxBootLoader instead of VZMacOSBootLoader, and VZGenericPlatformConfiguration instead of VZMacPlatformConfiguration. The device configuration (Virtio block, network, serial) remains largely identical. The vphone-cli project specifically targets macOS guests with PV=3 hardware.
How do I persist VM state between launches?
Save the VZMacMachineIdentifier data and reuse the same VZMacHardwareModel when recreating the configuration. The NVRAM file (VZMacAuxiliaryStorage) must also persist at a stable path. Do not regenerate identifiers—doing so presents a new virtual machine to the guest OS, invalidating activation state and licenses.
Why does my VM fail validation with "configuration is not supported"?
Check three common causes: insufficient host memory for requested memorySize, cpuCount below minimumAllowedCPUCount, or attempting to use VZMacGraphicsDeviceConfiguration on an incompatible hardware model. Validation errors include specific VZErrorCode values that pinpoint the failing configuration section.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →