怎么用 Avalonia 打包 iOS 应用并上架 App Store?
Avalonia 是 .NET 生态里跨平台 UI 的另一条路,和 MAUI 走的不是一套渲染思路——它自己画界面,所以在各平台上长得一模一样。iOS 这边它靠的是 .NET 的 iOS 工作负载,工程结构上会有一个单独的 .iOS 头项目(head project),最终由它产出 IPA。
因为底层用的是 .NET iOS SDK,编译签名这一步还是要 Apple 的工具链:要么在 macOS 上直接 dotnet publish,要么在 Windows 上通过 Visual Studio 的 Pair to Mac 转交,要么丢给 macOS CI。这步省不掉。但证书、描述文件、上传这些我用开心上架(AppUploader)在 Windows 上处理,没为了签个名去开钥匙串。
先说结论:哪一步真绕不开 Mac
| 环节 | 我实际怎么做的 |
|---|---|
| 注册 Bundle ID、签发证书、生成描述文件 | Windows 上做完 |
| 写代码、跑桌面端调试 | Windows |
| 编译签名成 IPA | 要 macOS——Pair to Mac 或 macOS CI |
| 提交 App Store | Windows 上传 |
| 装到 iPhone 测试 | 扫码注册 UDID,再扫码装 |
说明一下:我用的签证书工具本身在 macOS 上也能用(网页版不挑系统,桌面客户端有 macOS 版)。表里"不需要 Mac"是指这一步不再需要 Xcode、钥匙串访问或 Transporter。
第一步:在 .iOS 头项目里定好标识
Avalonia 模板生成的解决方案里会有个 YourApp.iOS 项目,iOS 的包标识写在它的 Info.plist 的 CFBundleIdentifier 里,也可以用 .csproj 的 ApplicationId 属性覆盖。
定成反向域名,比如 com.yourcompany.appname。顺便把 CFBundleShortVersionString(版本号)和 CFBundleVersion(build number)也确认一下——build number 每次上传都得比上次大,忘了改会直接被顶回来。
用到相机、相册、定位的话,对应的 NS*UsageDescription 也要在这份 Info.plist 里写全,缺了上传阶段会被打回。
第二步:在 Windows 上签出证书和描述文件
注册 Bundle ID:开心上架的「Bundle ID」页面点「新建 Bundle ID」,Identifier 填上一步那个值。创建后改不了。要用推送、App Groups 这些能力的,在这个 Bundle ID 的「权限」页面勾上再保存。
签发证书:「证书」页面点「新建证书」,上架选发布签名证书(对应 Apple Distribution),真机调试选开发签名证书(Apple Development)。绑定上面的 Bundle ID,填名称邮箱,设一个 .p12 保护密码——导入构建机钥匙串时要输,服务器不保存它。
勾上「使用开心上架服务同步证书」的话,构建机和团队成员都能下同一张证书,不用互相发文件。
生成描述文件:「描述文件」页面点「新建描述文件」,上架选 App Store 类型,内测选 Ad Hoc 并勾上测试设备,关联上面的 Bundle ID 和证书,提交后下载 .mobileprovision。
私钥和 CSR 都在本机生成后封进 .p12,不用在 Mac 上走一遍申请证书的流程。
第三步:把材料装到构建机上
不管是 Pair to Mac 连的那台还是 CI Runner,都是这两件事:
security import dist.p12 -k ~/Library/Keychains/login.keychain-db -P "你的p12密码" -T /usr/bin/codesign
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp AppStore.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
Visual Studio 在 Windows 上配好 Pair to Mac 之后,iOS 签名相关的下拉框读的就是这台 Mac 的钥匙串和描述文件目录。下拉框空的话先确认 Mac 上开了远程登录、装了对应版本的 Xcode——这基本都是环境问题,跟证书无关。
第四步:发布出 IPA
在 .iOS 项目的 .csproj 里指定手动签名用的证书和描述文件:
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<CodesignKey>Apple Distribution: Your Company (TEAMID)</CodesignKey>
<CodesignProvision>你的描述文件名称</CodesignProvision>
<ArchiveOnBuild>true</ArchiveOnBuild>
<RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
</PropertyGroup>
然后:
dotnet publish YourApp.iOS/YourApp.iOS.csproj -f net9.0-ios -c Release
(目标框架名按你实际用的 .NET 版本替换。)
这两个属性我在别的 .NET 项目上也踩过同样的坑,说一下:
CodesignKey填证书的通用名称,就是钥匙串里显示的那一整串。报找不到匹配项的话,通常是.p12没导进钥匙串,或者导进去的是不含私钥的.cer;CodesignProvision填描述文件的名称——你在开心上架创建时填的那个名字,不是文件名,也不是 UUID。
产物是个 .ipa,在 bin/Release/<tfm>/ios-arm64/publish/ 下面。
第五步:把 IPA 传上去
到这一步不需要 Mac 了。我一般先在开心上架的「文件&提交」页面把包打开看一眼签名信息,确认了再点「提交 App Store」,日志实时滚。
命令行版本,Windows/Linux 的 Runner 也能跑:
appuploader-cli upload -f YourApp.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop
-p 是 Apple 生成的 App 专用密码(xxxx-xxxx-xxxx-xxxx),不是登录密码。团队用 API 密钥更合适:
appuploader-cli upload -f YourApp.ipa --api-key <KeyID> --api-issuer <IssuerID> --private-key Auth_Key.p8
密钥方式只收 .ipa,且 App 要先在 App Store Connect 里建好。
第六步:发内测包
「设备」页面点「注册设备」,二维码发出去让人扫上报 UDID(一年 100 台,免费账号 3 台)。回描述文件那边点「重建」,生成一份包含这些设备的 Ad Hoc 描述文件。
把 CodesignProvision 换成这份描述文件重新 publish,然后在「文件&提交」页面生成二维码让人扫码装。
写在最后
Avalonia 的 iOS 头项目本质上就是个 .NET iOS 应用,签名那套和别的 .NET 项目是通的——搞明白 CodesignKey 和 CodesignProvision 分别要填什么,剩下就是重复劳动。那台 Mac 配好之后基本不用再登录。