M Logo
📁 iOS Development on Windows · Part 1 of 1

iOS Simulator on Windows: macOS Sonoma and Xcode in VMware on an AMD Ryzen PC

October 26, 2026

If you build iOS apps but your computer runs Windows, the usual advice is "buy a Mac". This guide shows another way that I actually got working: macOS Sonoma with Xcode and the iOS Simulator, running in a VMware virtual machine on a Windows PC with an AMD Ryzen CPU. At the end, an iPhone 15 running iOS 17.5 opens in a window you can click, and you can build and test with Xcode from the command line over SSH.

Everything below was done on one machine, in one long session, including the dead ends. I point out what I verified and what I did not.

Before you start: the honest warnings

  • Licensing. Apple's macOS license only allows macOS on Apple hardware. Running it in a VM on a PC goes against that license. Treat this as a personal experiment, and decide for yourself.
  • Unsupported. VMware does not support macOS guests on AMD CPUs. This guide relies on a community patch ("unlocker") and on changing the CPU information the VM reports. A VMware update can break it.
  • No GPU. A VM on Windows cannot use your graphics card for macOS. The Simulator draws on the CPU. It is fine for normal app screens and slow for games or heavy 3D.
  • Windows features you lose. VMware needs the Windows hypervisor turned off. That breaks Docker Desktop, WSL2 and Hyper-V virtual machines until you turn it back on. (A workaround for Docker is at the end.)
  • It takes hours. Mostly downloads and one tricky CPU setting.

What you need

  • A Windows 11 PC with at least 16 GB RAM (I gave the VM 12 GB) and about 100 GB of free SSD space. Keep the VM on an SSD, not a hard disk. I only tested an AMD Ryzen 5 9600X; other CPUs may need different CPU settings.
  • VMware Workstation (free for personal use, I used version 26).
  • A free Apple Account, for downloading Xcode from developer.apple.com.
  • About 40 GB of downloads: the macOS installer (about 14 GB), Xcode (about 3.4 GB) and the iOS Simulator runtime (about 7 GB).

My machine: Windows 11 Pro, AMD Ryzen 5 9600X, 32 GB RAM, NVMe SSD.

Which versions to choose (this matters)

I tried macOS Sequoia 15 first. Everything booted, but Xcode 15.4's Simulator app crashes on Sequoia with Symbol not found: _OBJC_CLASS_$__GCControllerManager. The simulator itself runs, but you get no window. The fix was macOS Sonoma 14 with Xcode 15.4, which is the combination Xcode 15.4 was built for.

PieceVersion used
macOSSonoma 14.8.9
Xcode15.4 (build 15F31d)
Simulator runtimeiOS 17.5
VM6 vCPU, 12 GB RAM, 150 GB growable disk

If you need a newer Xcode, you need a newer macOS, and the CPU settings below may need changes.

Step 1: Prepare Windows

Turn off the Windows hypervisor and virtualization-based security, and keep a way back.

  1. In an administrator terminal, back up and change the boot setting:
bash
bcdedit /export C:\backup\bcd-before.bak
bcdedit /set hypervisorlaunchtype off
  1. Turn off Memory integrity in Windows Security, Device security, Core isolation. If it is greyed out or comes back, also set EnableVirtualizationBasedSecurity to 0 under HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard (export the key first).
  2. Reboot, then try to start a VM in VMware. If macOS VMs start slowly or refuse to run, the hypervisor is probably still on.

To undo everything later: bcdedit /set hypervisorlaunchtype auto, restore the registry export, reboot.

Step 2: Install VMware and the unlocker

  1. Install VMware Workstation.
  2. Download the unlocker (I used the BDisp fork, "macOS Unlocker V3.0 for VMware Workstation", from its GitHub releases page). It patches VMware so you can pick macOS as the guest and so macOS can boot.
  3. Close VMware, then run the installer script (win-install.cmd) as administrator.

Two rules from its own README: never delete the unlocker's backup folder before uninstalling, and always uninstall an old unlocker before installing a new one. Any VMware update undoes the patch, so reinstall it afterwards.

Step 3: Get the macOS installer from Apple

Apple's software update catalog lists every full macOS installer. This PowerShell script downloads the catalog and prints the InstallAssistant.pkg links with their versions. Everything comes from Apple's own servers.

powershell
$u = 'https://swscan.apple.com/content/catalogs/others/index-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz'
curl.exe -sSL -o cat.gz $u
$in  = [IO.File]::OpenRead("$PWD\cat.gz")
$gz  = New-Object IO.Compression.GZipStream($in, [IO.Compression.CompressionMode]::Decompress)
$out = [IO.File]::Create("$PWD\cat.xml"); $gz.CopyTo($out); $out.Close(); $in.Close()
$t = [IO.File]::ReadAllText("$PWD\cat.xml")
$blocks = [regex]::Split($t, '(?=<key>\d{3}-\d{5}</key>\s*<dict>)')
foreach ($b in $blocks) {
  $m = [regex]::Match($b, '<string>(https://swcdn\.apple\.com/content/downloads/[^<]*?/InstallAssistant\.pkg)</string>')
  if (-not $m.Success) { continue }
  $dist = [regex]::Match($b, '(https://[^<]+\.English\.dist)').Groups[1].Value
  $x = curl.exe -sSL $dist
  $v = [regex]::Match($x, '<key>VERSION</key>\s*<string>([^<]+)</string>').Groups[1].Value
  "$v  $($m.Groups[1].Value)"
}

Pick the Sonoma 14.x line and download it with a download manager. Then check it is complete and genuine:

bash
# the size on your disk must equal the server's Content-Length
curl -sIL <the InstallAssistant.pkg URL> | grep -i content-length

Later, inside macOS, pkgutil --check-signature InstallAssistant.pkg must say "signed Apple Software".

Step 4: Get a macOS Recovery disk

The first boot needs something to start from. OpenCore's macrecovery script downloads Apple's recovery image from Apple's servers (pick the board ID line for the macOS you want from its README, and choose the macOS version you are installing):

bash
python macrecovery.py -b <BOARD_ID> -m <MLB> download

You get BaseSystem.dmg and BaseSystem.chunklist. Convert it into a VMware disk with qemu-img:

bash
qemu-img convert -f dmg -O vmdk BaseSystem.dmg BaseSystem.vmdk

Step 5: Create the VM and the CPU settings that make it boot

Create a new VM in VMware (guest OS: Apple macOS), but do not start it yet. Close VMware and edit the .vmx file. This is the full set I ended up with:

text
.encoding = "windows-1252"
config.version = "8"
virtualHW.version = "21"
displayName = "macOS Sonoma"
guestOS = "darwin24-64"
firmware = "efi"
uefi.secureBoot.enabled = "FALSE"
numvcpus = "6"
cpuid.coresPerSocket = "6"
memsize = "12288"
pciBridge0.present = "TRUE"
pciBridge4.present = "TRUE"
pciBridge4.virtualDev = "pcieRootPort"
pciBridge4.functions = "8"
pciBridge5.present = "TRUE"
pciBridge5.virtualDev = "pcieRootPort"
pciBridge5.functions = "8"
pciBridge6.present = "TRUE"
pciBridge6.virtualDev = "pcieRootPort"
pciBridge6.functions = "8"
pciBridge7.present = "TRUE"
pciBridge7.virtualDev = "pcieRootPort"
pciBridge7.functions = "8"
sata0.present = "TRUE"
sata0:0.present = "TRUE"
sata0:0.fileName = "BaseSystem.vmdk"
sata0:1.present = "TRUE"
sata0:1.fileName = "macOS.vmdk"
ethernet0.present = "TRUE"
ethernet0.connectionType = "nat"
ethernet0.virtualDev = "vmxnet3"
usb.present = "TRUE"
usb_xhci.present = "TRUE"
ehci.present = "TRUE"
mks.enable3d = "FALSE"
svga.vramSize = "268435456"
smc.present = "TRUE"
smc.version = "0"
hw.model.reflectHost = "FALSE"
hw.model = "iMac20,1"
board-id.reflectHost = "FALSE"
board-id = "Mac-AA95B1DDAB278B95"

Create the empty 150 GB system disk with vmware-vdiskmanager.exe -c -s 150GB -a sata -t 0 macOS.vmdk (it grows as you use it).

The CPU block (the hard part)

macOS expects an Intel CPU. On an AMD host you make the VM report an Intel one. Add these lines, which are what made Sonoma 14.8.9 boot:

text
cpuid.0.ebx = "0111:0101:0110:1110:0110:0101:0100:0111"
cpuid.0.ecx = "0110:1100:0110:0101:0111:0100:0110:1110"
cpuid.0.edx = "0100:1001:0110:0101:0110:1110:0110:1001"
cpuid.0.eax = "0000:0000:0000:0000:0000:0000:0001:0110"
cpuid.1.eax = "0000:0000:0000:0011:0000:0110:1100:0011"
cpuid.1.ebx = "0000:0010:0000:0001:0000:1000:0000:0000"
cpuid.1.ecx = "10--:--10:1-01:1000:001-:0010:0000:0011"
cpuid.1.edx = "0000:0111:1000:1011:1111:1011:1111:1111"
cpuid.4.eax = "0000:0000:0000:0000:0000:0001:0010:0001"
cpuid.4.ebx = "0000:0001:1100:0000:0000:0000:0011:1111"
cpuid.4.ecx = "0000:0000:0000:0000:0000:0000:0011:1111"
cpuid.4.edx = "0000:0000:0000:0000:0000:0000:0000:0000"
cpuid.7.ebx = "00-0:00--:--0-:--00:----:----:----:----"
cpuid.7.ecx = "----:----:----:----:-0-0:0---:-0--:--0-"
cpuid.7.edx = "----:----:0---:----:----:---0:----:00--"
cpuid.15.eax = "0000:0000:0000:0000:0000:0000:0000:0001"
cpuid.15.ebx = "0000:0000:0000:0000:0000:0000:0010:0111"
cpuid.15.ecx = "0000:0101:1111:0101:1110:0001:0000:0000"
cpuid.15.edx = "0000:0000:0000:0000:0000:0000:0000:0000"
cpuid.16.eax = "0000:0000:0000:0000:0000:1111:0011:1100"
cpuid.16.ebx = "0000:0000:0000:0000:0000:1111:0011:1100"
cpuid.16.ecx = "0000:0000:0000:0000:0000:0000:0110:0100"
cpuid.16.edx = "0000:0000:0000:0000:0000:0000:0000:0000"

What these do:

  • cpuid.0.* makes the vendor string GenuineIntel.
  • cpuid.1.eax reports a Haswell-generation CPU (family 6, model 0x3C). This matters, see below.
  • cpuid.1.ecx lets AVX through. The cpuid.7.* lines keep AVX2 but hide AVX-512.
  • cpuid.4.* gives a normal cache size (without it the log says "no linesize").
  • cpuid.15 and cpuid.16 tell the Intel kernel the CPU's clock speed, which it otherwise reads from a register that does not exist on AMD. Without them, a Haswell model crashes with a divide error.

Adjust the clock values for your CPU. cpuid.15.ecx is a 100 MHz base clock (100000000), and cpuid.15.ebx is your CPU's TSC frequency divided by 100 MHz. My TSC was 3.89 GHz, so 39. VMware prints your TSC in its vmware.log ("TSC Hz"). Put that same number in MHz into cpuid.16.eax and cpuid.16.ebx (3900 for me).

Why this was hard (what I learned)

The error you will hit first, on both Sequoia and Sonoma, is this boot panic:

text
launchd: Library not loaded: /usr/lib/libSystem.B.dylib ... no dyld cache
panic: initproc failed to start

It looks like a corrupt install, but it is not. Recent macOS versions ship their shared library cache only for Haswell-class CPUs (x86_64h). If the kernel thinks the CPU is older, it looks for a cache that does not exist and dies before the system starts.

  • On Sequoia it was enough to keep an older Intel model but let AVX through (hw.cpusubtype became 8).
  • On Sonoma the same settings did not work. The VM log showed the guest did receive AVX2, but the Sonoma kernel ignored it unless the CPU model looked like Haswell, and a Haswell model needs the clock values above.

You can check what the guest sees from Recovery's Terminal. On a working setup:

bash
sysctl hw.cpusubtype hw.optional.avx2_0

should print hw.cpusubtype: 8 and hw.optional.avx2_0: 1. If you see 4 and 0, the guest still looks like an old CPU.

How to read the real error

Do not guess. Send the guest's kernel messages to a text file. Add these lines to the .vmx:

text
serial0.present = "TRUE"
serial0.fileType = "file"
serial0.fileName = "C:\macOS-VM\serial.log"

and set verbose boot from Recovery's Terminal: nvram boot-args="-v serial=3". The panic text then appears in serial.log. Remove both when everything works.

Step 6: Install macOS

Boot the VM. If it shows a boot menu, choose the SATA disk that holds BaseSystem.vmdk. You land in macOS Recovery.

  1. Open Disk Utility and erase the empty disk as APFS, named Macintosh HD.
  2. Install macOS onto it.

There are two ways to get the very first macOS onto the empty disk:

  • Internet install (the simplest, but I did not try it in the final setup): choose "Reinstall macOS" in Recovery and let Apple's servers deliver the installer. It needs a working internet connection inside the VM.
  • From the installer you downloaded (what I did for my first macOS): put the installer on a second virtual disk so the VM can read it, expand the package, and run the startosinstall tool from the installer app against your empty disk. I do not give exact commands here, because this part took the most trial and error and my notes for it are incomplete. The tool lives inside the installer app (Contents/Resources/startosinstall) and prints its options with --help.

Either way, once you have any macOS booting, there is a cleaner route to the exact version you want:

Build a Sonoma installer inside a working macOS

I first got Sequoia running with the CPU settings above, then used it to build a clean Sonoma install disk. Inside the working macOS:

bash
sudo installer -pkg Sonoma_InstallAssistant.pkg -target /
hdiutil create -size 18g -layout GPTSPUD -fs 'HFS+J' -volname SonomaInstall -type UDIF media.dmg
hdiutil attach media.dmg
sudo "/Applications/Install macOS Sonoma.app/Contents/Resources/createinstallmedia" \
     --volume /Volumes/SonomaInstall --nointeraction
hdiutil detach "/Volumes/Install macOS Sonoma"
hdiutil convert media.dmg -format UDTO -o media_raw

You now have media_raw.cdr, a raw bootable installer disk. Attach a second empty 20 GB virtual disk to that VM (shut it down, edit the .vmx, boot again), find it with diskutil list, and write the installer onto it:

bash
sudo dd if=media_raw.cdr of=/dev/rdisk3 bs=4m

(Use your own disk number. Choosing the wrong one erases it, so check the size.) Shut the VM down. The new virtual disk file on Windows is now a bootable Sonoma installer. Make a new VM with the settings from Step 5, attach that disk plus a fresh empty 150 GB disk, boot from the installer disk, run Install macOS Sonoma, and select the empty disk. The VM restarts a few times. When it reaches the setup screens you are done with the installer.

Finish Setup Assistant yourself and create your user account. Do not share that password anywhere.

Step 7: Turn on SSH

Working in the macOS Terminal is far easier over SSH than through the VM window. In the VM's Terminal:

bash
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
mkdir -p ~/.ssh && chmod 700 ~/.ssh

On Windows, create a key (ssh-keygen -t ed25519) and append the contents of the .pub file to ~/.ssh/authorized_keys in the VM. Then from Windows, using the VM's IP address (ifconfig in macOS):

bash
ssh -i C:\macOS-VM\ssh\id_macvm your-user@192.168.130.129

Step 8: Install Xcode and the iOS Simulator

Download from developer.apple.com/download/all (a free Apple Account is enough) the files Xcode_15.4.xip and iOS_17.5_Simulator_Runtime.dmg. Get them onto the VM (Step 9 shows a fast way). Then verify them before installing. A genuine Apple file passes these checks, and a tampered copy will not:

bash
pkgutil --check-signature Xcode_15.4.xip      # must show "signed Apple Software"
xip --expand Xcode_15.4.xip                    # takes about 10 minutes
codesign --verify --deep --strict Xcode.app && echo CODESIGN_OK
spctl --assess --type execute -v Xcode.app     # must print: accepted, source=Apple System

Then install and set up:

bash
mv Xcode.app /Applications/
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch
xcrun simctl runtime add iOS_17.5_Simulator_Runtime.dmg
xcodebuild -version

Note that xcodebuild -importPlatform does not exist in Xcode 15.4. Use simctl runtime add.

Create and start an iPhone, and open the Simulator window:

bash
UDID=$(xcrun simctl create "iPhone 15" com.apple.CoreSimulator.SimDeviceType.iPhone-15 com.apple.CoreSimulator.SimRuntime.iOS-17-5)
xcrun simctl boot $UDID
open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app

The first start shows "Verifying iOS 17.5.simruntime" for a minute. Then the home screen appears. You can take screenshots with xcrun simctl io $UDID screenshot shot.png, install apps with simctl install, and run tests with xcodebuild test, all over SSH.

Step 9: Move big files into the VM quickly

If copying files over the network is slow, there are two causes worth checking.

The proxy trap. If you use a proxy program on Windows (I used Proxifier with a SOCKS proxy), its default rule may send traffic to your VMs through the proxy too. Most proxies cannot reach private addresses like 192.168.x.x, so SSH to the VM failed with "Connection refused" about half the time, and VM copies crawled at about 20 KB/s. I proved it by asking the proxy directly: public addresses worked, the VM address failed. The fix is one rule above the default one:

text
Name:    Local networks direct
Targets: 192.168.0.0/16; 10.0.0.0/8; 172.16.0.0/12
Action:  Direct

After that, 20 out of 20 SSH tests succeeded. I wasted hours before finding this.

A virtual disk instead of the network. For a huge one-off file, create a blank virtual disk on Windows, attach it to the VM, and write or copy the data on the VM side. This avoids network speed entirely (Step 6 used this for a 19 GB image, which took under a minute).

Step 10: Snapshot and clean up

  1. Remove the debug lines (serial0.*, the boot-args with sudo nvram -d boot-args).
  2. Delete installer files you no longer need.
  3. Shut the VM down and take a VMware snapshot, so you can always return to a working state.

Performance notes

Without a GPU, the Simulator is smooth for normal screens and slow for animation-heavy apps. Give the VM at least 8 GB of RAM (I used 12) and keep it on an SSD.

Troubleshooting cheat sheet

What you seeWhat it meansFix
Library not loaded: libSystem.B.dylib, panic loopmacOS thinks the CPU is too old for its shared cacheFix the CPU block, check hw.cpusubtype is 8
Divide error right after changing the CPU modelMissing clock values for a Haswell modelAdd cpuid.15 and cpuid.16
no linesize in the kernel logNo cache info in the CPU blockAdd cpuid.4.*
"No PCIe slot available for Ethernet0"The .vmx lacks PCI bridge entriesAdd the pciBridge* lines
"Unable to find a slot for PCI bridge"Old pciSlotNumber lines clashDelete all pciSlotNumber lines and restart
Simulator app quits instantly on SequoiaXcode 15.4 needs SonomaUse macOS Sonoma 14
SSH "Connection refused" sometimesA host proxy catches traffic to the VMAdd the "Local networks direct" rule
Docker Desktop stopped workingThe hypervisor must stay off for macOSSee below

Bonus: keeping Docker working

Because the Windows hypervisor has to stay off, I run Docker inside a small Ubuntu Server VM in the same VMware, and point the Windows docker command at it:

bash
docker context create docker-vm --docker "host=ssh://docker-vm"
docker --context docker-vm ps

Two things to know about a remote Docker engine: bind mounts in compose files are resolved on the VM, not on Windows, so the project needs to live on the VM; and move code with git, not by copying files, because Windows checkouts often have CRLF line endings that break shell scripts inside Linux containers.

Recap

  1. Turn off the Windows hypervisor, install VMware and the unlocker.
  2. Download the macOS Sonoma installer from Apple and check its size.
  3. Create the VM with the Intel-looking Haswell CPU block and your own clock values.
  4. Install macOS, finish setup yourself, and enable SSH.
  5. Install Xcode 15.4 and the iOS 17.5 runtime, verifying Apple's signatures first.
  6. Boot an iPhone simulator and open its window.
  7. Exclude your VM network from any proxy, and snapshot the result.

If you try this, the single most useful habit is to read the real kernel log before changing settings. It turned hours of guessing into one clear error message.

© 2026 All Rights Reserved By Mahmudul Alam