没有 Mac 也能做 iOS App:Windows 虚拟机 + Xcode Cloud + TestFlight 完整实录
AI摘要
在只有 Windows 电脑的情况下,我用 VMware 装了一台 macOS 虚拟机,只为完成一次 Xcode Cloud 配置,之后构建全在苹果云端跑,再通过 TestFlight 装到 iPhone。文章按实际操作顺序记录了开发者账号准备、标识注册、虚拟机与 Xcode 16.2 安装、工作流配置、首次构建排错,以及发布 TestFlight 时遇到的三次失败,并附上每一步的截图与日志。
想做 iOS 开发,绕不开 Xcode,而 Xcode 只能运行在 macOS 上。我手边只有一台装 Windows 的联想拯救者 Y9000P,最后走通的路线是:
在 Windows 上用 VMware 装一台 macOS 虚拟机,只用它完成一次 Xcode Cloud 的配置;之后每次构建都在苹果的云端完成,再通过 TestFlight 装到自己的 iPhone 上。
这篇文章按实际顺序记录了全过程:先试过、但没走通的两条路,不需要 Mac 的开发者账号准备,虚拟机安装,Xcode 16.2 安装,Xcode Cloud 工作流配置,首次构建排错,以及发布到 TestFlight 时遇到的三次失败。
本次实测进度: 以下环节都已实际完成并有截图或日志为证:macOS Sonoma 虚拟机安装、在虚拟机里安装并运行 Xcode 16.2、在 Xcode 中登录开发者账号、创建 Xcode Cloud 工作流、云端编译和模拟器 UI 测试全部通过、通过 TestFlight 把 App 装到 iOS 27 的 iPhone 上。
没有做的:文中的步骤都是实际操作过的,最终 App 通过 TestFlight 装到了 iOS 27 的 iPhone 上。USB 真机调试、VMware Tools 和共享文件夹这几条备选路线没有走,用到的地方会顺带说明。
这是一条在非苹果硬件上运行 macOS 的实验路线。macOS 的软件许可协议限制它在非苹果品牌的电脑上安装和使用,相关规定见 Apple 软件许可协议。下文的 Xcode Cloud、TestFlight 部分与虚拟机无关,用任何一台真正的 Mac 完成首次配置也是同样的步骤。
文中出现的邮箱、姓名、Team ID、Bundle ID、GitHub 用户名均已打码或替换为示例值,例如 com.yourname.yourapp、ABCDE12345、yourname/YourApp。
1. 为什么最后选了这条路线
1.1 先试过的两条路

图 17:Scaleway 的 Mac mini 创建页。PARIS 1 和 PARIS 3 的各个机型都显示 OUT OF STOCK;唯一没标缺货的 M2-L-ASAHI 装的是 Linux,不能装 Xcode。
1.2 关键认识:Mac 只需要用一次
Xcode Cloud 是苹果自己的云端构建服务,加入 Apple Developer Program 后每月包含 25 小时免费构建时长。它在苹果的服务器上编译、测试、打包,并能自动发到 TestFlight。
它唯一离不开 Mac 的地方,是第一次创建工作流:要在 Xcode 里把项目关联到 Xcode Cloud。我查了 App Store Connect 的网页和 API,只能查看、修改已有的工作流和启动构建,没找到新建产品的入口。
配置完成后,开始构建、看日志、改工作流都可以在 App Store Connect 网页上完成,不再需要 Mac。所以虚拟机只要能跑起 Xcode、登录账号、点完创建向导就够了,对性能要求不高。
1.3 完整流程一览
Windows 浏览器:开发者账号、注册 App ID、App Store Connect 建 App 记录
↓
Windows 上的 VMware:安装 macOS Sonoma 虚拟机
↓
虚拟机里:安装 Xcode 16.2 → 登录开发者账号 → 拉代码、生成工程 → 创建 Xcode Cloud 工作流(一次性)
↓
苹果云端:Xcode Cloud 编译、测试、打包、上传(以后每次都在这里)
↓
iPhone:安装 TestFlight → 安装自己的 App
2. 不需要 Mac 的准备工作
这一部分全部在 Windows 浏览器里完成。最好在装虚拟机之前就做完,因为后面工程里要填 Team ID 和 Bundle ID,Xcode Cloud 的创建向导也要关联现成的 App 记录。
2.1 加入 Apple Developer Program,找到 Team ID
Xcode Cloud 和 TestFlight 都要求加入 Apple Developer Program(年费 99 美元)。创建 Xcode Cloud 工作流时,你在团队里的角色需要是 Account Holder、Admin 或 App Manager 之一。
加入后,在 开发者账户页 的 Membership details 里能看到 Team ID:10 位大写字母和数字,例如 ABCDE12345。Team ID 不是密码,可以写进工程配置;Apple ID 密码、验证码、证书和私钥则任何时候都不要发给别人,也不要提交到代码仓库。
2.2 Bundle ID 怎么取
Bundle ID 是 App 的唯一标识,通常写成倒过来的域名形式,比如 com.yourname.yourapp。规则如下:
只能用英文字母、数字、连字符
-和点.,建议全部小写,不能有中文、空格、下划线。必须全球唯一。注册时苹果会自动检查,重复了就换一个。
不能用
com.example、com.apple这类保留或明显不属于你的前缀。不要求你真的拥有对应的域名,用英文名、网名或 GitHub 用户名都可以。
一旦用它在 App Store Connect 建了 App 记录,就再也不能改。 App 的显示名称以后可以随便改,Bundle ID 不行。
我的 App 带有小组件和分享扩展,所以一共要注册四个标识:
扩展的 ID 必须以主应用 ID 加 . 开头。
2.3 注册标识:先建 App Group,再建 App ID
打开 Identifiers 页面,点标题旁的蓝色 +,会看到下面这个页面:

图 18:注册新标识时先选类型。第一项是 App IDs,倒数第二项是 App Groups。
顺序不能反:App ID 需要关联已经存在的 App Group,所以先建组。
① App Group
选 App Groups,点右上角 Continue。
Description 填
YourApp Group。这一栏只能用字母、数字和空格。Identifier 填
group.com.yourname.yourapp。如果输入框前面已经带了group.,只填后面的部分。点 Continue,再点 Register。
② 主应用 App ID
回到列表,点 + → 选 App IDs → Continue → 类型选 App → Continue。
Description 填
YourApp。Bundle ID 选 Explicit,填
com.yourname.yourapp。往下滚到 Capabilities,勾选 App 实际用到的能力。我的 App 读取健康数据、在主应用与扩展间共享数据,所以勾了 App Groups 和 HealthKit;HealthKit 下面的 Clinical Health Records 等子项没有勾。
点 Continue → Register。
③ 扩展的 App ID
步骤同上,Description 和 Bundle ID 换成扩展的,Capabilities 只勾 App Groups。
④ 把三个 App ID 关联到组(容易漏)
只勾选 App Groups 还不够,还要指定是哪一个组:
在列表里点开一个 App ID。
在 App Groups 那一行点 Configure(有的显示为 Edit)。
勾选
group.com.yourname.yourapp,点 Continue。点右上角 Save。弹出 Modify App Capabilities 时点 Confirm。
另外两个 App ID 各重复一遍。
2.4 在 App Store Connect 建 App 记录
打开 App Store Connect → App → 左上角蓝色 + → 新建 App:
点 创建。App 名称和手机桌面上显示的名称(工程里的 CFBundleDisplayName)是分开的,但苹果要求两者相近,所以我同时把工程里的显示名称改成了一致的。
2.5 把真实标识写进工程
我的项目原来用的是 com.example 占位符和空的 Team ID。在继续之前,要把所有 Bundle ID、App Group 和 DEVELOPMENT_TEAM 都替换成刚注册的真实值,否则云端签名一定失败。
3. 电脑配置与版本选择

图 1:CPU 和硬件虚拟化满足这次实验的基本条件,16 GB 内存需要同时留给 Windows 和虚拟机使用。
3.1 为什么是 Sonoma + Xcode 16.2
虚拟机里的 Xcode 版本,取决于两件事:
项目要求的最低版本。 我的项目里有一个 Swift Package,
Package.swift第一行写的是swift-tools-version: 6.0。Swift 6 从 Xcode 16 开始提供,用 Xcode 15 打开工程会因为无法解析依赖包而识别不出 App,Xcode Cloud 的创建向导也就走不下去。所以最低要 Xcode 16.0。macOS 版本能装哪个 Xcode。 Apple 的兼容性表列出,Xcode 16.2 支持 macOS Sonoma 14.5 至 Sequoia 15.x,参见 Apple:Xcode 系统与 SDK 要求。
虚拟机里的 Xcode 版本不影响以后的构建。 它只用来创建一次工作流;以后每次构建用的是工作流里选的版本,在苹果的服务器上运行。本次虚拟机用 Xcode 16.2,云端用的是 Xcode 27,两者互不影响。
4. 需要下载的文件与网站
这次用到的入口集中放在这里。软件版本和网站界面以后可能变化,下面记录的是本次所用组合。
QEMU 的 Windows 安装包由构建维护者提供,该入口也列在 QEMU 官方下载页。macOS 恢复镜像不用另找网盘,后面通过 OC4VM 自带工具从 Apple 服务器下载。
5. 下载并安装 VMware:具体该选哪一项?
5.1 在 Broadcom 下载页面选 Windows 版
用 Windows 浏览器打开 Broadcom 免费下载页面。
按页面要求注册或登录 Broadcom 账号。
在产品搜索框输入
VMware Workstation Pro,点击 Show Results,再进入对应产品。在版本列表里找到 VMware Workstation Pro 25H2 for Windows,点击右边的小箭头展开。

图 2:截图上方默认展开的是 26H1。本次要找的是下方的“25H2 for Windows”。
页面上的几项很容易混淆,可以这样对应:
这里的 for Windows / for Linux 指 VMware 本身安装在哪个系统上。我们要把 VMware 装在 Windows 上,然后在 VMware 内运行 macOS。
OC4VM 项目列出的 Workstation 测试版本包括 17.6 和 25H2。本次采用 25H2 系列并实际安装了 25H2u1,依据和项目限制可查看 OC4VM 项目说明。
5.2 展开后有 25H2u1 和 25H2,选哪个?
本次点击上面的 25H2u1。

图 3:选择上方的 25H2u1;下方的 25H2 是同一系列的基础版本。
如果展开的是 26H1,里面也可能看到 26H1u1 和 26H1。它们属于另一个版本系列;想复现本文,返回后展开 25H2 for Windows。
5.3 在文件页点击下载
进入 25H2u1 页面后:
保持在 Primary Downloads 标签页。
阅读条款,勾选 I agree to the Terms and Conditions。
找到 VMware Workstation Pro for Windows 这一行。
点击该行最右侧的云朵向下箭头下载图标。

图 4:条款复选框在文件列表上方,下载图标在文件行的最右侧。
本次截图中的安装程序是:
VMware-Workstation-Full-25H2u1-25219725.exe
大小约 278.31 MB。以后页面出现的构建号可能不同,不要把本文的文件名当成所有版本的固定文件名。
5.4 软件安装目录与虚拟机目录分开
运行 .exe 后按安装向导继续。VMware 程序本身保留安装器默认目录比较省事;本次安装器显示的默认路径形式为:
C:\Program Files (x86)\VMware\VMware Workstation\
我尝试把盘符改成 D 盘时,出现了下面的安装目录权限提示:

图 5:这个提示针对 VMware 程序的安装目录。
遇到同样的提示,可以点击 确定 返回向导,再点击 Change…,恢复默认安装路径,然后继续安装。
虚拟机是另外一套文件,仍然放在:
D:\macOS-VM
这样,大体积的虚拟磁盘不会因为 VMware 程序装在 C 盘,就跟着全部放进 C 盘。
6. 安装 QEMU,解压 OC4VM,并确认目录
6.1 安装 QEMU
下载 QEMU Windows 64 位安装包,按向导安装。本次后续命令按默认安装目录编写:
C:\Program Files\qemu
在 Windows PowerShell 中验证:
& 'C:\Program Files\qemu\qemu-img.exe' --version
能显示版本信息,就说明用于转换镜像的程序可以运行。如果报“找不到路径”,先核对实际安装位置;不要继续运行后面的转换步骤。
6.2 将 OC4VM 解压到 D 盘
下载 oc4vm-3.0.1.zip 后,在资源管理器中解压,让下面这些文件夹直接出现在 D:\MacTools 内:
D:\MacTools
├─ config
├─ disks
├─ iso
├─ packages
├─ tools
│ └─ windows
│ ├─ recoveryOS.exe
│ └─ 其他工具
├─ vmware
│ ├─ intel
│ └─ amd
└─ LICENSE
本文实际使用的工具目录是:
D:\MacTools\tools\windows
这个路径中没有额外一层 OC4VM。 如果自己解压出来多了一层文件夹,可以在资源管理器里进入真正包含 tools、vmware 的目录,再把命令中的路径改为实际位置。
运行前可检查文件是否存在:
Test-Path 'D:\MacTools\tools\windows\recoveryOS.exe'
Test-Path 'D:\MacTools\vmware\intel\macos.vmx'
按本文目录解压时,两条命令都应返回 True。
7. 从 Apple 下载 macOS Sonoma 恢复镜像
7.1 到底在哪个目录执行?
在 Windows 的 D:\MacTools\tools\windows 目录执行。
直接打开 PowerShell,按顺序运行:
Set-Location -LiteralPath 'D:\MacTools\tools\windows'
$env:Path = 'C:\Program Files\qemu;' + $env:Path
.\recoveryOS.exe
第二行只给当前 PowerShell 窗口临时添加 QEMU 路径,关闭窗口后不会永久改变系统 PATH。每次新开窗口运行工具,都可以重新执行这一行。
也可以在资源管理器里进入 D:\MacTools\tools\windows,点击地址栏,输入 powershell 并回车,再运行后两行命令。
7.2 菜单选 Sonoma,再选 VMware VMDK
在本次工具版本的系统菜单中选择:
5. Sonoma
输入 5 后按回车。以后工具版本的菜单编号可能变化,优先核对 Sonoma 名称。
下载结束后出现输出格式菜单,选择:
1. VMware VMDK
输入 1 后按回车,等待转换完成。看到下面两句,就表示恢复镜像已准备好:
Created vmdk disk: sonoma.vmdk
Done! Your recoveryOS image is ready.

图 6:本次下载约 753 MB,完成 chunklist 校验后成功生成 sonoma.vmdk。
本次生成的文件位于:
D:\MacTools\tools\windows\sonoma.chunklist
D:\MacTools\tools\windows\sonoma.dmg
D:\MacTools\tools\windows\sonoma.vmdk
接下来要挂载的是 sonoma.vmdk。这里下载的是恢复环境,后面安装 Sonoma 时还要联网下载完整系统。工具依赖与镜像格式的说明可参考 OC4VM 安装前准备。
8. 复制 Intel 模板并打开虚拟机
8.1 准备虚拟机文件夹
因为这台 Y9000P 是 Intel 处理器,本次使用:
D:\MacTools\vmware\intel
在 D 盘新建 macOS-VM 文件夹,然后:
打开
D:\MacTools\vmware\intel。按
Ctrl+A选中其中所有文件,再按Ctrl+C。打开新建的
D:\macOS-VM,按Ctrl+V粘贴。把
D:\MacTools\tools\windows\sonoma.vmdk也复制到D:\macOS-VM。
如果 D:\macOS-VM 已经是装好或正在使用的虚拟机目录,另建一个空目录;不要把新模板覆盖到现有虚拟机上。
本次 OC4VM 3.0.1 Intel 模板包含的关键文件有:
D:\macOS-VM
├─ macos.vmx
├─ macos.vmdk
├─ macos.nvram
├─ macos.plist
├─ opencore.iso
├─ opencore.vmdk
└─ sonoma.vmdk ← 自己下载后复制进来的恢复镜像
macos.vmdk 是将来安装系统的磁盘,sonoma.vmdk 是临时恢复环境,OpenCore 文件负责引导。复制整个模板,不要只拿一个 .vmx 文件。 本路线使用 OC4VM 模板,不额外执行 Unlocker 补丁,参见 OC4VM 项目介绍。
8.2 在 VMware 中打开配置文件
在 VMware 中点击:
File → Open…
选择:
D:\macOS-VM\macos.vmx
如果 VMware 询问虚拟机是移动还是复制过来的,这里选择 I Copied It。打开后会看到名为 macOS INTEL 的虚拟机。
8.3 分配内存、处理器与网络
在关机状态点击 Edit virtual machine settings,本次使用下面的配置:

图 7:本次采用 8 GB 内存、4 个虚拟处理器和 NAT 网络。截图时尚未展示后续添加的恢复磁盘。
CPU 很强,不等于要把所有线程交给虚拟机。16 GB 内存的机器同时运行 Windows、浏览器和 macOS,很容易出现内存紧张。这套配置后来跑 Xcode 16.2、打开项目、创建 Xcode Cloud 工作流都够用;编译和测试都在云端,不需要虚拟机有多强。
模板里的虚拟磁盘标称 500 GB,属于可增长的虚拟磁盘,不会在一开始立刻占用宿主机 500 GB。但写入数据后文件会增大,D 盘真实剩余空间仍然是上限,要给系统和 Xcode 留空间。
8.4 把 sonoma.vmdk 作为第二块 SATA 磁盘添加
仍在虚拟机设置窗口里操作:
点击 Add…。
选择 Hard Disk,点击 Next。
磁盘类型选择 SATA,点击 Next。
选择 Use an existing virtual disk,点击 Next。
点击 Browse…,选择
D:\macOS-VM\sonoma.vmdk。点击 Finish。
如果弹出磁盘格式转换提示,本次选择 Keep Existing Format。
点击 OK 保存设置。
添加后会有系统磁盘和恢复磁盘两块硬盘。不要用恢复镜像替换模板里的系统磁盘,也不要把 .vmdk 当作光盘 ISO 挂载。
9. 启动并安装 macOS Sonoma
9.1 开机后出现 Removable Devices 提示
点击 Power on this virtual machine 启动。第一次开机时,我看到了 VMware 的 Removable Devices 提示:

图 8:这是可移动设备连接方式的说明,可以点击 OK 继续。
如果不想重复看到它,可以勾选 Do not show this hint again,再点击 OK。
鼠标进入虚拟机后,按 Ctrl+Alt 可释放回 Windows。安装过程中 VMware 工具栏上的暂停按钮可能显示为两条橙色竖线;按钮外观本身不能证明虚拟机已经暂停,不要为了“恢复”而反复点击它。
9.2 在恢复界面选择重新安装
等待 OpenCore 引导进入恢复环境,选择中文后,会看到下面的界面:

图 9:本次已经成功进入 Sonoma 恢复环境。
点击 重新安装 macOS Sonoma,再点击 继续,按照安装器显示的许可步骤操作。
9.3 安装磁盘选 Macintosh HD
在选择磁盘的界面,本次出现了:
Macintosh HD
OPENCORE
macOS Base System
选择 Macintosh HD,然后点击 继续。
OC4VM 模板已经带有预格式化的 APFS 系统磁盘,本次直接选择它安装。OPENCORE 是引导盘,macOS Base System 是恢复环境,都不是安装目标。
如果页面中根本没有 Macintosh HD,先返回 VMware 核对模板系统磁盘是否仍然存在并已连接。 本次没有通过“抹掉宿主机硬盘”来解决问题,也没有格式化 Windows 的 C 盘或 D 盘。
9.4 联网下载与自动重启
选择目标磁盘后,安装器开始下载和准备系统。本次初期显示:
剩余大约 2 小时 32 分钟

图 10:这时已经进入在线安装阶段,恢复镜像下载完成不等于完整系统已经下载完成。
安装期间可能自动重启多次。保持 VMware 和电脑运行,正常情况下 OpenCore 会继续引导安装流程。若停在引导菜单,安装尚未结束时留意 macOS Installer;安装结束后进入 Macintosh HD。不要再次主动选择恢复环境重新发起安装。
安装流程和引导阶段可以对照 OC4VM:Installing macOS。
10. 实际踩坑:两小时不动,后来变成十几个小时
10.1 先看安装日志,确认它在做什么
后面安装器的预计时间变成了:
剩余大约 12 小时 57 分钟

图 11:预计时间明显变长,但单靠这个数字无法判断是不是已经卡死。
这时可以在安装器顶部菜单中打开 窗口 → 安装器日志。如果 VMware 把 Windows 快捷键截走了,优先使用菜单操作。
把日志级别切到 显示所有日志,滚动到最新内容。本次看到了:
Retrieving 1 packages (13.655 GB)
Started downloading package com.apple.pkg.InstallAssistant.macOSSonoma
下载地址属于 swcdn.apple.com。

图 12:本次日志给出了下载包大小与 Apple CDN 地址,说明当时的重点是大文件下载。
日志里还有跳过 bridgeOS 等信息。这种跳过信息本身不能当成安装失败;需要结合后续错误、网络流量和磁盘活动判断。
10.2 先给宿主机释放内存与带宽
最开始 Windows 任务管理器中的内存使用已经接近 96%。虚拟机分配了 8 GB,而浏览器和其他软件也在使用内存,这会给整个电脑带来压力。
我排查时优先做的是:
保存工作,退出暂时不用的高内存软件。
关闭不需要的浏览器标签和后台下载。
暂停网盘同步、其他大文件下载和视频播放。
给笔记本接电,保持网络稳定,并避免 Windows 自动睡眠。
保持虚拟机当前安装过程运行。
排查后宿主机可用内存曾回升到约 4 GB,但这不能单独证明安装速度已经加快。若当前阶段主要在下载,CPU 和磁盘并不一定是瓶颈。
安装进行中不要为了“让路”而强制结束 VMware、反复暂停虚拟机,或临时大改虚拟硬件。这些操作可能打断当前下载和安装。
10.3 十几个 GB 大致要下载多久?
以约 13.655 GB 的数据量、持续稳定的速度做粗略估算:
这里按十进制 GB/MB 计算,未计入网络波动、重试、校验和安装写盘,也不是剩余时间预测。安装器可能按最近一小段时间的速度更新预计时间,所以实际观察下载量比盯着倒计时更有用。
11. 系统安装完成后:跳过迁移和 Apple ID 登录
11.1 迁移助理点击“以后”
迁移助理页面会问是否从 Mac、时间机器备份或 Windows PC 迁移资料。这次是新建开发实验环境,不需要导入 Windows 上的个人文件。
点击左下角 以后。

图 15:点击左下角“以后”,继续新系统设置。
11.2 Apple ID 登录报错怎么办?
我尝试在首次设置中登录 Apple ID,先出现了:
com.apple.AppleAccount.Error 错误 -1
随后又出现 无法激活 iCloud 的服务器错误提示:

图 16:只保留没有显示登录邮箱的错误弹窗截图。
遇到这一情况,可以按下面的顺序继续设置:
点击错误弹窗中的 好。
如果后面停在 iCloud 条款窗口,点击 取消,返回 Apple ID 登录页。
点击登录页左下角 稍后设置。
如有确认窗口,选择跳过或以后设置。
创建 macOS 的本地用户,设置用户名和密码,继续首次设置。
OC4VM 的安装说明也提示首次设置应跳过 Apple 账号,参见 项目安装说明中的账号步骤。
好消息是,这个错误不影响后面的工作。 系统层面的 iCloud 登录、浏览器登录 Apple Developer 网站、Xcode 里登录开发者账号,是三件不同的事。本次 iCloud 一直没有激活成功,但后面在 Safari 里登录开发者网站下载 Xcode、在 Xcode 的 Accounts 里登录开发者账号,都成功了(见第 13 节)。
后续首次设置按自己的需要选择外观、分析数据、屏幕使用时间等选项,本地账号建好后进入桌面。
12. 在虚拟机里安装 Xcode 16.2
12.1 直接在虚拟机的 Safari 里下载
我原本计划在 Windows 上下载 Xcode,再通过 VMware 共享文件夹传进虚拟机。实际操作时,直接在虚拟机里打开 Safari 下载更省事:
在虚拟机里打开 Safari,进入 Apple Developer 全部下载页。
用自己的 Apple 账号登录。
搜索
Xcode 16.2,找到 Xcode 16.2.xip(页面显示约 2.77 GB),点击下载。注意不要下成 Command Line Tools for Xcode,那只是命令行工具,不是完整的 Xcode。

图 19:虚拟机里的 Safari 打开了 Apple 下载页,Xcode 16.2.xip 已下载、展开并打开。
这样就不需要安装 VMware Tools,也不用配置共享文件夹。如果虚拟机里网络很慢,再考虑“Windows 下载 + 共享文件夹或 exFAT U 盘传输”的办法,这条路本次没有实测。
下载历史版本的 Xcode 需要登录 Apple 账号,但不要求加入付费开发者计划,见 Apple:Xcode 下载资源。
12.2 展开并打开
在“下载”文件夹里双击
.xip,等待 macOS 校验并展开。2.77 GB 的归档展开要一段时间,期间保持足够的磁盘空间。把展开得到的 Xcode 拖进 应用程序。
打开 Xcode,同意许可,等它安装首次运行组件。
我这里展开后,应用程序文件夹里的名字是 Xcode 2.app,Dock 上也出现了两个 Xcode 图标。这个名字中间带空格,后面在终端里会踩坑(见 12.3)。

图 20:Xcode 16.2 在虚拟机里正常启动。
打开项目后,Xcode 顶部会提示 iOS 18.2 Not Installed 和一个 Get 按钮。不用点:本地只用来创建工作流,不在虚拟机里编译和运行模拟器,iOS 模拟器运行时用不到。
12.3 终端里的 xcodebuild 用不了:切换开发者目录
在终端里执行 xcodebuild -version,第一次得到的是:
xcode-select: note: No developer tools were found, requesting install.
之后再执行,变成了:
xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory
'/Library/Developer/CommandLineTools' is a command line tools instance
意思是终端默认用的是一套精简的“命令行工具”,没有指向刚装的完整 Xcode。后面检查项目时要在终端里用 xcodebuild,所以要让它指向 Xcode:
① 先找到 Xcode 装在哪
ls /Applications | grep -i xcode
我这里输出的是 Xcode 2.app。
② 切换开发者目录
我第一次手打成了 Xcode2.app,少了中间的空格,得到:
xcode-select: error: invalid developer directory '/Applications/Xcode2.app/Contents/Developer'
为了避免手打出错,改用下面这条,它会自动找到第一个以 Xcode 开头的应用:
sudo xcode-select -s "$(ls -d /Applications/Xcode*.app | head -1)/Contents/Developer"
sudo 要求输入的是 这台 macOS 的本地登录密码,输入时屏幕上不显示任何字符,输完直接回车。成功时没有任何输出。
如果应用就叫 Xcode.app,也可以直接写:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
③ 同意许可并确认版本
sudo xcodebuild -license accept
xcodebuild -version
如果第一条显示了一大段许可协议,最后提示 By typing 'agree'...,就输入 agree 回车。第二条的输出是:
Xcode 16.2
Build version 16C5032a
13. 在 Xcode 里登录开发者账号
即使第 11 节里 iCloud 一直激活不了,Xcode 里的开发者账号也能正常登录:
先点一下 Xcode 窗口,确认屏幕最上面的菜单栏左边显示的是 Xcode,而不是“访达”。
点 Xcode → Settings…,或按 Command + 逗号。在 VMware 里,Windows 键就是 Command 键。
点顶部的 Accounts 标签 → 左下角 + → Apple Account → Continue。
输入开发者 Apple ID 和密码。手机上弹出验证码时,填进来。

图 21:登录成功后,右侧 Team 列表里出现了自己的团队,角色是 Admin。邮箱和团队名已打码。
角色是 Admin(或 Account Holder、App Manager)才能创建 Xcode Cloud 工作流和 App 记录。
14. 虚拟机里的几个实用技巧
这些都是实际操作中反复遇到的小问题。
14.1 Windows 和虚拟机之间不能复制粘贴
没装 VMware Tools 时,在 Windows 里复制的命令粘贴不进虚拟机。装 VMware Tools 的方法见 OC4VM 项目说明(VMware macOS Guest Tools),本次没有实测。
我用的办法更简单:在虚拟机的 Safari 里打开存放命令的网页(比如自己的在线笔记或文档),直接在虚拟机里复制、粘贴到终端。虚拟机内部的复制粘贴一直是好用的。
在 VMware 里,复制是 Win + C,粘贴是 Win + V。
14.2 粘贴后命令前面出现 ^[[200~
有一次粘贴后,终端里出现了:
zsh: bad pattern: ^[[200~sudo
这是粘贴时多带了几个控制字符。按 Ctrl + C 清掉这一行,再粘贴一次就好了。
14.3 每次只粘贴一行,等出现 % 再粘贴下一行
我曾经把同一条下载解压命令粘贴了两次。第二次解压时,unzip 停下来问:
replace xcodegen/LICENSE? [y]es, [n]o, [A]ll, [N]one, [r]ename:
接着粘贴的下一条命令被当成了这个问题的回答,出了一串 invalid response。遇到这种情况,按 Ctrl + C 退出提问,重新从漏掉的那一行开始。每次只粘贴一行,等它跑完、重新出现提示符 % 后再粘贴下一行。
14.4 鼠标与按键
鼠标被虚拟机“吃掉”时,按
Ctrl + Alt释放回 Windows。虚拟机里需要右键时,如果右键不灵,可以按住
Ctrl再点左键。
15. 让项目能被 Xcode Cloud 构建
15.1 工程文件不提交,靠脚本在云端生成
我的项目用 XcodeGen 管理:仓库里只有一个 project.yml,.xcodeproj 是生成出来的,并且写进了 .gitignore,不提交。这样在 Windows 上也能改工程配置,不会遇到 .xcodeproj 合并冲突。
问题是 Xcode Cloud 克隆仓库后,仓库里没有工程文件。解决办法是在仓库根目录放一个 ci_scripts/ci_post_clone.sh。Xcode Cloud 克隆代码后会自动执行这个脚本,在它检查工程之前把工程生成出来:
#!/bin/sh
# Xcode Cloud runs this right after cloning. The .xcodeproj is generated from project.yml and not
# committed, so generate it here.
set -eu
cd "${CI_PRIMARY_REPOSITORY_PATH:-$(dirname "$0")/..}"
export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1
command -v xcodegen >/dev/null 2>&1 || brew install xcodegen
xcodegen generate
要点:
文件名必须是
ci_post_clone.sh,放在ci_scripts文件夹里;ci_scripts要和.xcodeproj在同一层目录。文件必须有可执行权限。在 Windows 上可以用
git update-index --chmod=+x ci_scripts/ci_post_clone.sh设置。要用
ci_post_clone.sh,不能用ci_pre_xcodebuild.sh:后者执行时,Xcode Cloud 已经检查过工程了,那时工程还不存在。
Apple 文档提醒,动态生成的工程可能导致 Xcode Cloud 配置失败。本次实测,Xcode 接受了这种方式。如果你遇到“找不到工程或 scheme”,备用办法是把生成的 .xcodeproj 一起提交(见第 22 节)。
15.2 用的 scheme 要是共享的,并开启 Archive
Xcode Cloud 只能看到 共享(shared) 的 scheme,构建 App 的 scheme 还要开启 Archive 动作。XcodeGen 生成的 scheme 默认就是共享的。用手动维护的工程时,要在 Product → Scheme → Manage Schemes 里勾选 Shared。
16. 在虚拟机里拉代码、生成工程(不装 Homebrew)
如果是借来的 Mac,不想装太多东西,可以不装 Homebrew,直接下载 XcodeGen 和 GitHub 命令行工具 gh 的可执行文件,放在一个临时文件夹里,用完整个删掉。
16.1 下载两个工具
一行一行执行:
mkdir -p ~/app-setup && cd ~/app-setup
curl -L -o xcodegen.zip https://github.com/yonaskolb/XcodeGen/releases/latest/download/xcodegen.zip && unzip -q xcodegen.zip && echo XCODEGEN-OK
GH_URL=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep -oE 'https://[^"]*macOS_(universal|amd64)\.zip' | head -1); echo "$GH_URL"
curl -L -o gh.zip "$GH_URL" && unzip -q gh.zip && mv gh_*_macOS_* gh && echo GH-OK
第 2 行最后打印
XCODEGEN-OK,第 4 行最后打印GH-OK,就是成功了。第 3 行会自动找到 gh 最新版的 macOS 安装包地址并打印出来。本次在 Intel 虚拟机上找到的是
gh_2.102.0_macOS_amd64.zip。如果打印出来是空行,说明没取到地址,先不要执行第 4 行。
16.2 登录 GitHub
私有仓库需要登录才能克隆:
./gh/bin/gh auth login
它会依次问几个问题,用方向键选择、回车确认:
然后终端会显示:
! One-time code (XXXX-XXXX) copied to clipboard
Press Enter to open https://github.com/login/device in your browser...
先在终端按回车,它才会开始等待授权。浏览器会打开 GitHub 的设备授权页,登录后输入这 8 位代码,点 Continue,再点 Authorize。网页显示 “Congratulations, you're all set!” 后,终端会出现:
✓ Authentication complete.
✓ Logged in as yourname
这个代码几分钟后就会失效。如果过期了,按 Ctrl + C,重新执行 gh auth login。只输入你自己终端里显示的代码,不要输入任何别人发给你的代码。
16.3 克隆、生成工程并检查
./gh/bin/gh repo clone yourname/YourApp
cd YourApp
../xcodegen/bin/xcodegen generate
xcodebuild -project YourApp.xcodeproj -describeAllArchivableProducts -json
xcodegen generate最后应该显示Created project at .../YourApp.xcodeproj。最后一条命令可能要等一两分钟,因为 Xcode 要先解析工程和依赖包。它会列出所有可归档的产品,Xcode Cloud 的创建向导用的就是同一种识别方法。输出里应该能看到主应用和各个扩展,以及正确的 Bundle ID 和 Team:
{
"bundleIdentifier" : "com.yourname.yourapp.widgets",
"containingSchemes" : [ "YourApp" ],
"displayName" : "YourAppWidgets",
"productType" : "appex",
"team" : "ABCDE12345"
}
这一步如果报错,在终端里能看到具体原因,比在图形界面的向导里卡住、只看到一句模糊提示好排查得多。都正常的话,打开工程:
open YourApp.xcodeproj
打开后等顶部的 “Packages | Finishing...” 消失,依赖包就处理完了。“Indexing” 不用等。如果弹窗问是否信任某个包或插件,点 Trust & Enable。
17. 创建 Xcode Cloud 工作流
17.1 打开创建向导
点 Xcode 左侧边栏顶部那一排图标里最右边那个(Report navigator),或者按 Command + 9。
左侧上方出现 Local | Cloud 两个按钮,点 Cloud。
点 Get Started,弹出欢迎窗口,点 Next。

图 22:欢迎窗口会显示“Welcome, 你的团队名 developer team”,说明团队识别正确(已打码)。
17.2 选择产品
Select a Product 窗口里会列出 App 和 Framework 两类。选 App 那一行,不要选自己的 Swift Package(Framework)。

图 23:选中 App 那一行,平台 iOS,团队是自己的团队。Bundle ID 和团队名已打码。
17.3 先改默认设置,再继续
点 Next 后进入 Review Workflow,Xcode 给出的默认工作流是:

图 24:默认的开始条件是 Branch Changes (main),也就是每次推送到 main 都会自动构建。
默认设置会在每次推送代码时自动构建,很快就会把每月 25 小时的免费额度用掉。先点左下角 Edit Workflow…,不要直接点 Next:
① 开始条件改成手动
点左侧 Start Conditions 旁边的 ⊕,选 Manual Start。Source Branch 选 Any Branch 或 Custom Branches 里填
main都可以。选中左侧的 Branch Changes,点右上角的垃圾桶图标删除它。
左侧 Start Conditions 下面应该只剩 Manual Start - Branch。

图 25:添加 Manual Start,Branch 开关打开,Source Branch 选 Any Branch;Pull Request 和 Tag 保持关闭。
② Environment 选 Latest Release
Xcode Version 和 macOS Version 都选 Latest Release,不要选成虚拟机里的 16.2。本次 Latest Release 是 Xcode 27。
③ 加一个 Test 动作,只用一台模拟器
点 Actions 旁边的 ⊕ → Test。
Platform 选 iOS,Scheme 选自己的 App,Test 选 Use Scheme Settings。
测试设备(Destination)默认是 Recommended iPhones,这是一组设备,每台都要各跑一遍,会成倍消耗时长。点它,在 iPhone 分组里选一台具体机型。

图 26:Destination 改成了 iPhone 15,系统 iOS 27.0;Requirement 是 Required To Pass,测试不过整次构建就算失败。
④ Archive 先保持 None
第一次构建只验证能不能编译和测试通过,Archive 的 Distribution Preparation 先保持 None。发 TestFlight 的设置放到第 19 节。
点 Save,回到 Review Workflow 确认:

图 27:Start Condition 是 Manual Start - Branch,Actions 是 Test (iOS) 和 Archive (iOS)。
17.4 授权 GitHub、关联 App 记录、开始构建
点 Next,出现 Grant Access to Your Source Code,点 Grant Access。
浏览器会依次跳到 App Store Connect 和 GitHub。在 GitHub 上安装 Xcode Cloud 应用时,选 Only select repositories,只勾选这一个仓库。
回到 Xcode,仓库前面出现绿色对勾,点 Next。

图 28:GitHub 仓库前出现绿色对勾,说明 Xcode Cloud 已经能读取代码(账号和仓库名已打码)。
Xcode 自动识别出第 2.4 节建好的 App 记录,点 Complete。

图 29:Confirm App on App Store Connect,自动关联到事先建好的 App 记录。
出现 Start Build 窗口,分支选
main,点 Start Build。

图 30:Build 1 已创建,Start Condition 显示 Manual,状态是 Queuing。
到这里,虚拟机的任务就完成了。以后在 App Store Connect 网页上操作:App → 你的 App → Xcode Cloud → 构建版本 → 启动构建版本。
18. 第一次构建:用录屏而不是猜测找原因
18.1 Build 1:打包成功,6 项测试失败 1 项
Build 1 运行了约 21 分钟:
Archive 成功:在 Xcode 27 上完整编译、打包通过,说明工程、Bundle ID、签名团队和
ci_post_clone.sh都没问题。Test 6 项通过 5 项:失败的是一个 UI 测试,报错只有一行
XCTAssertTrue failed,指向测试文件第 203 行。

图 31:App Store Connect 网页上只能看到失败的测试名和行号;3 个警告是 iOS 17 起弃用的录音权限 API,不影响功能。
这个测试在之前 GitHub 上的 iOS 26 环境里是通过的,这次换成了 iOS 27、iPhone 15。原因有好几种可能,光看报错没法判断。
18.2 在 Xcode 里看失败那一刻的录屏
Xcode Cloud 会为 UI 测试自动录屏,网页上看不到,要在 Xcode 里看:
Report navigator → Cloud → 展开 App → 工作流 → 点那次构建。
展开 Test - iOS → 点 Tests → 找到带红叉的测试,点开。
切到 Activities:左边是每一步操作(Tap、Swipe up、Waiting for…),右边是录屏。
点左边任意一行,右边的录屏就跳到那一刻。
失败那一刻的画面出乎意料:屏幕上显示的是“完成与取消”列表页,测试早就离开了它应该停留的规则编辑页。

图 32:失败前是一长串 Swipe down,失败时屏幕上已经是另一个页面了。
往前找,填完某个输入框之后,中文键盘一直开着:

图 33:iPhone 15 屏幕较小,键盘占了下半屏,屏幕正中间刚好落在键盘的候选栏上。
整个过程就清楚了:测试的“向上滑动找按钮”滑在了键盘上,表单根本没动;找不到按钮后,它改成向下滑,结果把整个编辑页(一个从底部弹出的页面)下拉关掉了。
18.3 修复与经验
修复分两部分:
App 本身:编辑页有未保存的修改时,禁止下拉关闭,点“取消”要先确认是否放弃修改;滑动表单时自动收起键盘。这本来就是一个真实的体验问题:用户填到一半,手一滑就全没了。
测试脚本:输入完按回车收起键盘;键盘开着时,改在键盘上方的区域滑动。
Build 2 两项全部通过,6 个测试全部成功。
经验:先看录屏,再动手改。 失败报错里只有一个行号,真正的原因只有看到当时的画面才知道。
19. 发布到 TestFlight:又失败了两次
19.1 建一个内部测试群组
App Store Connect → App → 你的 App → 顶部 TestFlight 标签。
左侧 内部测试 旁边的 + → 创建内部群组,起个名字,勾选 启用自动分发。
进入群组,点 测试员 旁的 +,把自己加进去。
内部测试最多 100 人,必须是你 App Store Connect 团队里的用户,不需要苹果审核。
19.2 修改工作流
在 App Store Connect 网页的 Xcode Cloud → 工作流程 → 你的工作流 里修改,不需要 Mac:
归档 - iOS → 分发准备:从“无”改成 App Store Connect。
点 后续操作 旁的 ⊕,选 TestFlight 内部测试,工件选“归档 - iOS”,群组选刚建的群组。
点右上角 保存。

图 34:分发准备改为 App Store Connect;后续操作是 TestFlight 内部测试,群组里 1 名成员。
如果“TestFlight 内部测试”选不了,通常是分发准备还停在“无”。
19.3 Build 3:ad-hoc 和开发版导出失败
Build 3 的 Archive 失败了,日志里能看到:

图 35:app-store 导出成功,ad-hoc 和 development 两种导出各报两次 “Command exited with non-zero exit-code: 70”。
Xcode Cloud 在分发准备选 App Store Connect 时,会一次导出三种版本。后两种是给开发者直接装到“指定手机”上用的,它们的描述文件必须绑定开发者账号里至少一台已登记的设备。我的账号里一台设备都没有,这两种就导出失败了;一失败,整个 Archive 被判失败,后面的 TestFlight 步骤也被跳过,TestFlight 里一个版本都没有。
解决:在开发者账号里登记一台 iPhone,只需要做一次。
查 iPhone 的 UDID。在 Windows 上,用 Microsoft Store 里的 Apple 设备(Apple Devices)应用或 iTunes,数据线连接手机,手机上点“信任此电脑”。在设备信息页,点几下序列号那一行,它会切换显示成 UDID。新款 iPhone 的 UDID 形如
00008110-001A2B3C4D5E801E(25 位,中间有一个横杠)。右键复制。打开 Devices 页面,点 +。Platform 选 iOS 那一类,起个名字,粘贴 UDID,点 Continue → Register。
在 App Store Connect 里重新启动构建。Xcode Cloud 会自动重新生成包含这台设备的描述文件。
登记后,Build 4 没有再出现这两个导出错误。
这一步只有开发者自己要做一次。 别人用你的 App 时:
这三种方式都不需要别人提供 UDID。
19.4 Build 4:提交 App Store Connect 时被退回
Build 4 的导出全部成功,但卡在最后一步:

图 36:错误只有一句 “Preparing build for App Store Connect failed”,日志里也没有更多细节。
具体原因要看邮箱。开发者账号的邮箱里收到了 App Store Connect 发来的邮件:
ITMS-90683: Missing purpose string in Info.plist - Your app's code references one or more
APIs that access sensitive user data, or the app has one or more entitlements that permit
such access. The Info.plist file for the "YourApp.app" bundle should contain a
NSHealthUpdateUsageDescription key with a user-facing purpose string ...
我的 App 只读取健康数据(步数、运动、睡眠),所以当初只写了读取的用途说明 NSHealthShareUsageDescription。但只要 App 带有 HealthKit 能力,苹果就要求写入的说明 NSHealthUpdateUsageDescription 也必须有,不管实际用不用。我照实写了:“本 App 不会向‘健康’写入任何数据;系统要求使用健康读取功能的 App 同时声明此项。”
顺便把代码里所有涉及隐私的功能对照检查了一遍,免得再被退回:
另外,在 Info.plist 里设置 ITSAppUsesNonExemptEncryption = NO(App 只用系统的 HTTPS 时),每次上传就不用再手动回答出口合规问题。
经验:Xcode Cloud 上“准备提交 App Store Connect 失败”时,第一时间去翻邮箱。 苹果服务器端检查出的问题,会以 ITMS 编号的邮件发给账号邮箱,里面写明具体缺什么,有时会进垃圾邮件文件夹。
19.5 Build 5:成功,装到了 iPhone 上
补上用途说明后,Build 5 的测试、归档、TestFlight 内部测试三步全部成功。上传后苹果还要“处理”一段时间,处理完会发邮件通知。
在 iPhone 上:
在 App Store 搜索并安装 TestFlight(蓝色螺旋桨图标,开发者是 Apple)。手机系统需要不低于 App 的最低版本,我的 App 最低要求 iOS 17。
用开发者 Apple ID 打开 TestFlight,App 会出现在列表里,点 安装。
如果手机上登录的是别的 Apple ID,开发者邮箱会收到 TestFlight 邀请邮件,在手机上打开邮件,点里面的链接接受邀请即可。
我的 iPhone 是 iOS 27,App 成功装上并正常打开。
TestFlight 装的是 Release 配置构建的版本,和正式上架的版本一致。只在 Debug 配置里打开的功能,在 TestFlight 版本里不会出现。
20. 关于真机调试:这次没走 USB 这条路
最初的计划是把 iPhone 用 USB 接进虚拟机,用 Xcode 16.2 直接真机调试。这条路本次没有实测,也没有必要:
我的手机是 iOS 27,Xcode 16.2 自带的是 iOS 18.2 SDK,能否配对和调试本来就不确定。
TestFlight 版本由云端的 Xcode 27 构建,和手机系统版本匹配,实测可以正常安装运行。
日常验证靠 Xcode Cloud 上的模拟器 UI 测试,加上 TestFlight 真机试用,已经覆盖了大部分需求。
如果你一定要在虚拟机里做 USB 真机调试,可以按下面的顺序尝试,但这些步骤我没有验证过:在 VMware 中选 VM → Removable Devices → 你的 iPhone → Connect (Disconnect from Host);手机上点信任;Xcode 里打开 Window → Devices and Simulators 看能否识别;按提示在手机 设置 → 隐私与安全性 里打开 开发者模式。开发者模式的说明见 Apple:启用设备开发者模式。另外,OC4VM 项目说明非苹果硬件不能使用 Apple 的半虚拟化 GPU,虚拟机里的模拟器和界面性能不能和真 Mac 比,见 OC4VM 功能限制。
21. 费用与时间
时间上,最耗时的是 macOS 安装(受网络影响,几个小时)和下载 Xcode。Xcode Cloud 的配置本身,从打开工程到点下 Start Build,不到一个小时。
22. 安全与收尾
Apple ID 密码、验证码、证书和私钥不要发给任何人,也不要提交到代码仓库。Xcode Cloud 用的是云端自动管理的签名,不需要上传证书。
授权 Xcode Cloud 访问 GitHub 时,只选需要的那一个仓库。
工作流设为 只能手动开始,避免推送代码就自动消耗额度。
22.1 借来的 Mac 用完要清理
配置完成后,在终端执行:
~/app-setup/gh/bin/gh auth logout
rm -rf ~/app-setup
再到 Xcode → Settings → Accounts,选中自己的账号,点左下角 − 删除;App Store 也退出登录。
22.2 备用方案:提交生成的工程
如果 Xcode Cloud 不接受生成的工程,提示找不到工程或 scheme,就在虚拟机里把生成的工程一起提交,然后重新创建工作流:
sed -i '' '/^\*\.xcodeproj\/$/d' .gitignore
git add .gitignore YourApp.xcodeproj
git commit -m "Commit generated Xcode project for Xcode Cloud"
git push origin main
ci_post_clone.sh 仍会在云端用 project.yml 重新生成工程,两者保持一致。本次没有用到这个方案。
23. 踩坑速查表
24. 本次结果
已实际完成,并有截图或日志为证:
在 Windows 上通过 VMware + OC4VM 安装 macOS Sonoma,进入桌面。
在虚拟机的 Safari 里从 Apple 官网下载 Xcode 16.2,展开、运行,
xcodebuild -version输出Xcode 16.2 / Build version 16C5032a。首次设置时 iCloud 激活失败,但 Xcode 中的开发者账号登录成功。
不装 Homebrew,在虚拟机里克隆私有仓库、用 XcodeGen 生成工程,
describeAllArchivableProducts正确识别出 App 和扩展。创建 Xcode Cloud 工作流:只能手动开始,Latest Release 环境,单设备 UI 测试。
Xcode Cloud 上编译、归档、6 项模拟器 UI 测试全部通过。
解决 ad-hoc/development 导出失败(登记设备)和 ITMS-90683(补用途说明)后,通过 TestFlight 把 App 装到了 iOS 27 的 iPhone 上。
本次没有做、没有验证的:
在虚拟机里安装 VMware Tools、使用共享文件夹。
USB 连接 iPhone 到虚拟机进行真机调试。
TestFlight 外部测试和 App Store 正式提交审核。
虚拟机在整个流程里只用了一次。以后改完代码推送到仓库,在 App Store Connect 网页上点一下“启动构建版本”,二三十分钟后新版本就会出现在手机的 TestFlight 里。
记录日期:2026 年 10 月 6 日。 图片为本次操作截图;含登录邮箱、姓名、团队标识、Bundle ID、GitHub 账号的位置已打码,终端输出改用文字呈现并替换为示例值。软件、系统与设备兼容性要求以相关项目和 Apple 当前文档为准。