☰
iOS ipa转换工具环境配置与代码签名真机验证
2026/11/4 8:59:57 网站建设 项目流程

简介:这份资源面向需要在iOS设备上安装非App Store渠道应用的开发者与进阶用户,聚焦于把.app等软件包重新打包、签名并转换为可安装IPA格式这一实际问题,属于偏工具实操与原理理解的中等门槛资料。压缩包共31个文件,约447KB,以15个png界面截图和若干cpp、h源码为主,另有plist配置、dsp与dsw工程文件、rc资源脚本及一个已编译的exe可执行程序,从代码到成品工具的结构相对完整。已有10876人学习下载,说明其在iOS侧载与格式转换场景中具备一定参考价值。读者可从中获得转换流程的源码实现思路,包括包内容提取与重新封装、Info.plist元数据调整、代码签名环节的处理方式,以及工程组织与界面资源的搭配关系;配合截图可快速理解工具的运行形态,也便于在此基础上改造成自己的打包脚本或排查签名失败的原因。使用时需注意遵守苹果开发者协议与版权规定,仅用于合法合规的开发测试场景。

1. ipa转换工具配不对,真机上那个灰图标就是最早的通知

把一个.app目录拖进压缩包、后缀改成.ipa,发给测试同事,装完图标是灰的,点一下直接弹回桌面——这是很多人第一次用 ipa 转换工具时交的学费。它不是那种双击就能用的傻瓜软件,而是一套跟 Xcode 命令行工具、代码签名、描述文件绑死的命令行环境:Xcode 版本差一个小号、xcode-select指到了 Command Line Tools、打包时丢了可执行权限,任何一处不对,产物在 Mac 上看着人畜无害,到了真机上就是装不上、或者装上了闪退。这套环境适合手上已经有.app或.xcarchive、需要稳定产出可安装 ipa 的 iOS 开发和测试同学,也适合要给外部团队发内测包的运维。下面按我实际装过的一台 Mac 的顺序走,从版本选型写到真机安装验证。

2. 环境选型:macOS、Xcode 与命令行工具的版本对照

装这套东西最容易翻车的地方不在工具本身,而在它脚下那层:macOS 版本决定了你能装哪个 Xcode,Xcode 版本决定了命令行工具的行为,命令行工具又决定了打包出来的 ipa 能不能被设备接受。很多人一上来就xcode-select --install,装完一个 1GB 出头的 Command Line Tools 就以为齐活了,结果跑导出命令的时候报一句含糊的错,再花半天去搜。先把这三层版本对齐,后面能省掉八成玄学问题。

2.1 三个版本号必须先对齐

Xcode 对 macOS 有硬性的最低版本要求,装不上的时候系统只会告诉你"需要更高版本的系统",不会告诉你该退到哪一版。常用组合我整理成下面这张表,照着选基本不会踩空:

macOS 版本可用的 Xcode说明
13.5 Ventura15.0 至 15.4Xcode 16 系列装不上,别硬试
14.5 Sonoma15.4 / 16.x目前最稳的组合,推荐
15.x Sequoia16.x老脚本注意 Apple Silicon 的架构问题

磁盘空间是第二个容易被忽略的门槛。Xcode 本体解压后 15GB 上下,加上 iOS 平台支持和模拟器运行时,实际占用轻松过 40GB;转换工具本身、Python 运行时、几个设备的备份缓存再加一点。我一般要求机器至少留 100GB 空闲再动手,不然装到一半提示空间不足,删东西比装东西还费时间。

工具链这边还有一层:Apple Silicon 的机器跑那些为 x86_64 编译的老版本转换脚本或签名辅助程序,会直接报Bad CPU type in executable。这不是工具坏了,是缺 Rosetta 转译层,一条命令补上:

# Apple Silicon 机器上补装 Rosetta 2,Intel 机器跳过这一步 softwareupdate --install-rosetta --agree-to-license # 装完确认当前机器架构,后面判断二进制能不能直接跑要用 uname -m # arm64 表示 Apple Silicon,x86_64 表示 Intel

--agree-to-license是免交互确认许可,不加的话会卡在终端里等你按回车,写进自动化脚本会直接挂住。uname -m的输出建议记一下,后面装依赖、判断某个二进制能不能跑,第一件事就是看这个。

2.2 只装 Command Line Tools 是最常见的错误

Command Line Tools 和完整 Xcode 是两回事:前者只提供编译器、codesign、xcodebuild的一部分能力,后者才带完整的 SDK、模拟器和导出流程。只装 CLT 的情况下,编译能过,但一旦走到xcodebuild -exportArchive或者需要 iOS SDK 的环节就会失败,而且报错信息往往指向别处,特别误导。

正确的顺序是先装完整 Xcode。可以从 App Store 装,也可以从开发者下载页拿.xip安装包,装完一定是/Applications/Xcode.app。然后把它指成当前开发目录:

# 把命令行工具指向完整 Xcode,这是最关键的一步 sudo xcode-select -s /Applications/Xcode.app/Contents/Developer # 接受许可协议,不执行的话 xcodebuild 会在第一次调用时直接退出 sudo xcodebuild -license accept # 安装 Xcode 自带的首批组件(模拟器运行时等),需要联网 sudo xcodebuild -runFirstLaunch

xcode-select -s里的-s是 switch,把系统级的开发者目录切过去。这三条命令的顺序不能乱:没切目录就执行xcodebuild -license,操作的是 CLT 那一套;没接受许可就-runFirstLaunch,会直接返回许可未同意的错误。-runFirstLaunch视网络情况可能跑十几分钟,别以为卡死了就 Ctrl+C,中断之后组件装一半,后面会更麻烦。

2.3 装完必须校验的四条命令

装完不校验,等于没装。我固定跑这四条,任何一条输出不对就停下来查,不要往下走:

# 1. 确认指向的是完整 Xcode,而不是 /Library/Developer/CommandLineTools xcode-select -p # 期望:/Applications/Xcode.app/Contents/Developer # 2. 确认 Xcode 版本号 xcodebuild -version # 3. 确认 iOS SDK 已经就位 xcodebuild -showsdks | grep -i iphoneos # 4. 确认机器架构 uname -m

第一条命令的输出是最重要的判据。如果打印的是/Library/Developer/CommandLineTools,说明系统里同时存在两套工具链且当前指向 CLT,这时候哪怕xcodebuild -version能打印出版本号,导出 ipa 那一步也大概率会失败。第二条给出版本号,跟前面表格对照一下,确认跟 macOS 匹配。第三条要看得到iphoneos开头的 SDK 行,没有的话说明 iOS 平台组件没装全,回 Xcode 的 Settings 里补下载。

2.4 依赖层:Homebrew 与 Python 运行时

转换工具本身依赖 Python 运行时和几个跟 iOS 设备通信的库,用 Homebrew 装最省事。Homebrew 装完第一件事是把 shellenv 写进配置文件,否则新开一个终端窗口brew就找不到了:

# Apple Silicon 默认装在 /opt/homebrew,Intel 机器是 /usr/local echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile eval "$(/opt/homebrew/bin/brew shellenv)" # 确认可用 brew --version

这台机器我优先用 Python 3.12,太新的 3.13 有些依赖轮子还没跟上,编译时会退回去源码构建,耗时长还容易缺头文件;3.9 以下的版本则可能被某些依赖直接拒绝。装的时候别覆盖系统自带的 Python,macOS 自带的那个版本是给系统组件用的,动它容易出连锁问题。

3. 安装 ipa转换工具:虚拟环境、命令行入口与 PATH

工具本体通常就是一个命令行程序,仓库里带一份依赖清单。我吃过最大的亏是直接pip install到全局环境,装完跟别的项目里已有的库版本打架,报错报得莫名其妙。后来固定做法是给它单开一个虚拟环境,从系统层面隔开,坏了整个目录删掉重来就行。

3.1 先装运行时依赖

设备安装和签名校验会用到几个命令行工具,提前装上,省得转换完了才发现装不进设备:

# Python 运行时:工具本身跑在上面 brew install python@3.12 # libimobiledevice:跟 iOS 设备通信的底层库,读设备信息、查 UDID 都靠它 brew install libimobiledevice # ideviceinstaller:命令行装 ipa、列已装应用,验证环节的主力 brew install ideviceinstaller

libimobiledevice这一套是开源的设备通信实现,idevice_id -l能列出当前连着的设备 UDID,ideviceinfo能读设备型号和系统版本,ideviceinstaller负责安装和卸载。这三个命令在后面的验证和排查里会反复用到,装完顺手跑一下确认能识别设备:

# 设备用数据线连上 Mac 并点"信任"之后执行 idevice_id -l # 能打印出一串 40 位十六进制 UDID 就说明通信正常 ideviceinfo -k ProductVersion # 打印设备当前的 iOS 版本,用于判断 ipa 的 Deployment Target 是否兼容

读不到设备的时候先别怀疑库,八成的锅在数据线或者没点"信任"。换一根原装线、把设备解锁后重新插拔一次,再用idevice_id -l试。

3.2 用独立虚拟环境装工具本体

仓库可以 clone 到本地,也可以下压缩包解压,位置我习惯放在~/tools/下面,路径短、权限干净、不放在 iCloud 同步目录里(同步目录会带扩展属性,打包时会污染 ipa 里的文件)。装的时候建虚拟环境,再把工具装进去:

# 1. 建一个专用虚拟环境,路径固定下来,后面写进 PATH python3.12 -m venv ~/.local/venvs/ipa-convert # 2. 激活它 source ~/.local/venvs/ipa-convert/bin/activate # 3. 升级打包工具本身,老版本 pip 装某些包会报构建失败 pip install --upgrade pip setuptools wheel # 4. 在工具仓库根目录执行本地安装,点号表示安装当前目录 pip install .

第 1 步的-m venv后面跟的是目录路径,目录不存在会自动建。第 4 步的.不能省,它不是"当前目录里随便找找",而是告诉 pip 读取当前目录的pyproject.toml或setup.py做本地安装。如果仓库里只有一个可执行脚本而不是标准包,那就跳过 pip,直接给脚本加执行权限:

# 脚本形态的仓库用这个方式 chmod +x ~/tools/ipa-convert/ipa-convert

chmod +x加的是执行位,缺了它,直接敲命令会报Permission denied,这个错我见过不止一次,尤其在从压缩包里解压出来的文件上——有些解压工具会丢掉权限位。

3.3 把入口挂到 PATH 上

虚拟环境激活只在当前终端窗口有效,新开一个窗口就失效了。要长期可用,两条路:一条是每次手动source activate,另一条是把虚拟环境的bin目录写进PATH。我用后者,写进~/.zshrc:

# ~/.zshrc 追加以下内容 # 虚拟环境的 bin 目录,里面放着工具的入口命令 export IPA_CONVERT_VENV="$HOME/.local/venvs/ipa-convert" export PATH="$IPA_CONVERT_VENV/bin:$PATH" # 转换过程产生的中间文件放这里,别散落在项目目录里 export IPA_TOOL_WORKDIR="$HOME/Library/Caches/ipa-convert" mkdir -p "$IPA_TOOL_WORKDIR"

这里三个东西值得说一下。PATH的拼接顺序是把虚拟环境的bin放在最前面,这样同名命令优先命中这一份,不会被系统里别的版本抢走。IPA_CONVERT_VENV单独抽成变量,是为了后面脚本里引用时只改一处。IPA_TOOL_WORKDIR指向缓存目录而不是/tmp,是因为/tmp重启就清空,而调试的时候经常要回头翻上一次的中间产物,放缓存目录能多留几天。

写完之后执行source ~/.zshrc,或者干脆关掉终端重开一个,然后验证入口能不能找到:

# 确认命令能被解析到虚拟环境里的那一份 which ipa-convert # 期望输出以 /Users/你的用户名/.local/venvs/ipa-convert/bin/ 开头 # 打印版本和帮助,确认程序能正常启动 ipa-convert --version ipa-convert --help

which的输出路径必须落在虚拟环境目录里。如果打印的是/usr/local/bin/ipa-convert或者什么都没打印,说明PATH没生效或者入口名不对,回去检查~/.zshrc有没有写错、当前 shell 是不是 zsh。

3.4 授权文件的落位

签名相关的证书和描述文件不在工具目录里,而在系统的钥匙串和~/Library/MobileDevice/Provisioning Profiles/下面。证书通过 Xcode 登录开发者账号后自动同步,描述文件可以从开发者后台下载后双击导入,也可以手动放到上面那个目录。检查证书有没有就位:

# 列出钥匙串里所有可用的代码签名身份 security find-identity -v -p codesigning # 列出本机已安装的描述文件数量 ls -1 ~/Library/MobileDevice/Provisioning\ Profiles/ | wc -l

-v表示只列有效证书,-p codesigning限定用途为代码签名。输出里出现0 valid identities found是最典型的翻车信号,说明证书没同步下来或者过期了,这时候不管转换逻辑写得多对,产物都签不上,装到设备上一样是灰图标。

4. 配置转换参数:Payload 结构、签名与 exportOptions

工具装好只是第一步,真正决定 ipa 能不能装进设备的是参数配置。这块我见过太多人只改输入输出路径就开跑,跑完拿着产物问为什么装不上。ipa 不是普通的压缩包,它对目录层级、文件权限、签名覆盖范围都有明确要求,配置项本质上是在满足这些要求。

4.1 ipa 的本质是一个特定结构的 zip

一个能被设备接受的 ipa,解压之后顶层必须只有一个Payload目录,Payload里面直接放.app,.app里面才是二进制、资源、Info.plist。多一层目录、少一层目录、把.app直接压到根上,都会导致安装失败,而且失败信息通常只有一句The provided update package is invalid,看不出具体问题在哪。

标准的目录结构是这样的:

Payload/ └── MyApp.app/ ├── MyApp # 主二进制,必须有执行权限 755 ├── Info.plist # 包信息,CFBundleExecutable 要和二进制同名 ├── embedded.mobileprovision # 描述文件,签名后由工具写入 ├── PkgInfo ├── Frameworks/ # 内嵌动态库和 Swift 运行时 ├── PlugIns/ # 扩展(今天的 widget、分享扩展) └── Assets.car / *.png ... # 资源文件

两个细节最容易出错。第一是主二进制的权限:MyApp这个文件必须是755,用ls -l看应该是-rwxr-xr-x。打包过程中如果用了某些解压再压缩的流程,权限位会被抹成644,装上去就是点不开。第二是Info.plist里的CFBundleExecutable必须和二进制文件名完全一致,改过产物名字却没同步改这个字段,设备会认为包结构损坏。

4.2 配置文件写法与参数取舍

工具支持用 YAML 配置文件描述一次转换,比每次敲一长串参数可靠得多,也方便版本管理。我常用的配置长这样:

# ~/.config/ipa-convert/config.yaml # 输入:可以是 .app 目录,也可以是 .xcarchive input: ./build/MyApp.app # 输出:建议带版本号和构建号,避免覆盖后分不清是哪个包 output: ./dist/MyApp-1.4.2-218.ipa # 包标识,必须与描述文件里的 App ID 匹配 bundle_id: com.example.demoapp # 版本号与构建号,设备升级判断依赖这两个字段 version: "1.4.2" build: "218" sign: # 从 security find-identity 的输出里原样复制 identity: "Apple Development: A Developer (XXXXXXXXXX)" provisioning_profile: ./profiles/demo.mobileprovision entitlements: ./config/app.entitlements package: # 压缩级别 0-9,6 是体积和耗时的平衡点 compression: 6 # 保留原始文件权限,这个必须是 true preserve_permissions: true # 清理扩展属性,iCloud 同步过的文件会带一堆 strip_xattrs: true
参数作用取值范围我一般填
compressionzip 压缩级别0 到 96,再高耗时涨得比体积降得快
preserve_permissions保留文件权限位true / falsetrue,关掉必翻车
strip_xattrs清理扩展属性true / falsetrue,防止 iCloud 元数据混进包
versionCFBundleShortVersionString字符串与构建系统一致
buildCFBundleVersion字符串递增整数

version和build一定要写成字符串,YAML 里1.4会被解析成浮点数,写进 plist 之后变成1.4这种看似正常实则可能丢精度的值;1.4.2不加引号更会直接解析失败。这个坑很隐蔽,配置文件校验通过、转换也成功,但设备上显示的版本号不对,升级流程会一直认为有新版本。

4.3 签名配置:从内到外,别用 --deep

签名的顺序必须是从最内层往外签:Frameworks里的每个.framework或.dylib、PlugIns里的每个.appex、嵌套的 Watch App,最后才是主.app。反过来签,外层签完再改内层,外层签名立刻失效。

# 1. 先进到解压出来的暂存目录 cd "$IPA_TOOL_WORKDIR/stage" # 2. 签内嵌的 framework,有几个签几个 codesign --force --timestamp=none \ --sign "Apple Development: A Developer (XXXXXXXXXX)" \ Payload/MyApp.app/Frameworks/SomeSDK.framework # 3. 签扩展 codesign --force --timestamp=none \ --sign "Apple Development: A Developer (XXXXXXXXXX)" \ --entitlements ./config/extension.entitlements \ Payload/MyApp.app/PlugIns/ShareExtension.appex # 4. 最后签主包,带主包的 entitlements codesign --force --timestamp=none \ --sign "Apple Development: A Developer (XXXXXXXXXX)" \ --entitlements ./config/app.entitlements \ Payload/MyApp.app

--force是覆盖已有签名,重复签的时候必须加,不加会报already signed。--timestamp=none是跳过时间戳服务器,本地开发和内测阶段不需要,加上反而会拖慢速度,还容易因为网络问题卡住;正式分发才需要时间戳。--entitlements指定权限文件,扩展和主包的权限不一样,不能用同一份。

至于--deep,我的建议是别用。它会递归地把所有嵌套内容重签一遍,看起来省事,但它签内嵌 framework 时不带各自的 entitlements,而且对某些带特殊权限的扩展处理不正确,签出来的包在部分系统版本上会闪退。这个参数在旧教程里出现频率极高,属于典型的"能跑但不知道什么时候会坏"。

4.4 打包命令与压缩级别

内容准备好之后,用ditto打包而不是普通的zip。ditto会保留符号链接、权限位和扩展属性,这三点对 ipa 都是硬要求:

# 创建输出目录 mkdir -p ./dist # -c 创建归档,-k 生成 zip 格式, # --sequesterRsrc 把资源派生数据单独存放, # --keepParent 保留 Payload 这一层目录名,缺了它顶层就散架 ditto -c -k --sequesterRsrc --keepParent Payload ./dist/MyApp-1.4.2-218.ipa

--keepParent是最关键的一个开关。不加它,压缩包里会直接是MyApp.app,少了Payload这一层,设备会直接判定包结构不合法。如果用zip命令,对应的写法是:

# -q 安静模式,-r 递归,-y 保留符号链接 zip -qry ./dist/MyApp-1.4.2-218.ipa Payload

zip的-y不能省,Swift 项目里Frameworks目录下的 framework 内部大量使用符号链接,不保留链接会变成实体文件副本,包体积暴涨不说,签名校验也会因为文件哈希变化而失败。

5. 排查记录:五条装坏了的现场与救法

这套环境出问题的表现高度相似,都是"装不上"或者"装上了闪退",但根因分布得很散。下面五条是我自己踩过、也帮别人定位过的,按现象、原因、解决三段记下来,遇到时照着对。

5.1 桌面出现灰色图标,点击后立即消失

现象:ipa 能装进设备,进度条走完,桌面出现图标但是灰色的,点一下图标闪一下就没了,设备不重启也不报错。

原因:最常见的是主二进制缺少执行权限。zip打包时如果没加-y或者用了某些图形化解压工具重新压过一次,MyApp的权限位会从755掉成644。设备装的时候不校验这个,启动的时候发现不可执行,就直接终止进程,表现出来就是闪退。

解决:解压 ipa,对主二进制重新授权再重签重打:

# 进到解压目录后执行 chmod 755 Payload/MyApp.app/MyApp # 确认权限正确,应该是 -rwxr-xr-x ls -l Payload/MyApp.app/MyApp # 重新签名并打包 codesign --force --timestamp=none --sign "$IDENTITY" Payload/MyApp.app ditto -c -k --sequesterRsrc --keepParent Payload ./dist/repacked.ipa

改完权限必须重签,因为权限位变了,文件哈希就变了,原签名立刻失效。

5.2 安装报 0xE8008015 或 ApplicationVerificationFailed

现象:ideviceinstaller -i直接返回ApplicationVerificationFailed,或者设备上弹窗提示无法验证应用。

原因:签名用的证书、描述文件、entitlements 三者对不上。典型情况是描述文件里的 App ID 是com.example.demoapp,而实际包的bundle_id被改成了别的;或者 entitments 里申请了描述文件没开通的能力(比如推送、关联域名),设备校验时直接拒绝。

解决:把三份信息摊开对比。先看包里的标识,再看描述文件里的权限:

# 读包里实际的 bundle id plutil -extract CFBundleIdentifier raw Payload/MyApp.app/Info.plist # 解开描述文件,看它授权的 App ID 和权限 security cms -D -i Payload/MyApp.app/embedded.mobileprovision > /tmp/profile.plist plutil -extract Entitlements xml1 -o - /tmp/profile.plist

两边的application-identifier后缀必须一致,Entitlements里列出的能力必须是描述文件的子集。多申请一项就是签名不匹配。

5.3 报 Bad CPU type in executable

现象:转换脚本跑到一半退出,终端打印Bad CPU type in executable,没有任何其他线索。

原因:在 Apple Silicon 机器上执行了只为 x86_64 编译的二进制,通常是仓库里自带的某个辅助工具或者旧版本的签名工具。系统不会自动转译,直接拒绝执行。

解决:先确认是哪个文件挂的,再决定是补 Rosetta 还是换架构:

# 看某个二进制的架构 file $(which ipa-convert) # 输出包含 arm64 说明原生支持,只有 x86_64 就需要 Rosetta # 补装转译层 softwareupdate --install-rosetta --agree-to-license

如果仓库提供 Apple Silicon 版本,优先换成原生版本,转译层能跑但速度会慢一截,而且部分涉及系统调用的操作在转译下行为不完全一致。

5.4 导出时报 No profiles for 'xxx' were found

现象:xcodebuild -exportArchive直接失败,日志里一行No profiles for 'com.example.demoapp' were found,但确信描述文件已经装过了。

原因:exportOptions.plist里的method字段跟证书类型不匹配。用开发证书却写成app-store,或者用 Ad Hoc 描述文件却写development,导出流程会去找对应类型的描述文件,找不到就报这个错,而不会提示你类型写错了。

解决:把method改成和证书一致的值再导:

# exportOptions.plist 里的关键字段 /usr/libexec/PlistBuddy -c "Print :method" ./exportOptions.plist # 输出应为 development / ad-hoc / app-store 之一,与签名证书类型对应

开发证书对应development,内测分发用ad-hoc,上架才是app-store。改完重新导出,别在旧产物上反复试。

5.5 转换中途退出,退出码 1 且没有日志

现象:命令跑了几秒就退出,终端只有一行exit code 1,没有堆栈没有提示。

原因:工具的日志默认写到工作目录下的日志文件里,终端只输出摘要。没有设置IPA_TOOL_WORKDIR的情况下,日志会落在当前目录,很多人 cd 到别处就找不到了。

解决:显式指定工作目录,把日志固定到一个地方,然后看最后几十行:

# 指定工作目录并开启详细日志 IPA_TOOL_WORKDIR="$HOME/Library/Caches/ipa-convert" ipa-convert --config ./config.yaml --verbose # 看最新的日志文件 ls -lt "$HOME/Library/Caches/ipa-convert"/*.log | head -1 tail -n 50 "$(ls -t "$HOME/Library/Caches/ipa-convert"/*.log | head -1)"

--verbose打开详细输出,大部分"没有日志"的情况其实是默认级别太低。日志目录里通常会分成转换、签名、打包三段,看最后一段的时间戳,能快速定位卡在哪个环节。

6. 验证闭环:从 ipa 文件到真机桌面图标

产物生成出来,别急着发给测试。我固定做四层验证,每层对应一类失败,从外到内逐层收窄,能把问题卡在最小范围里。第一层看结构,第二层看签名,第三层看描述文件权限,第四层才上真机。

# 第 1 层:结构验证,确认 Payload 层级和文件权限 unzip -l ./dist/MyApp-1.4.2-218.ipa | head -20 # 期望第一行是 Payload/,下面紧跟 Payload/MyApp.app/ # 解压后单独看主二进制权限 unzip -q ./dist/MyApp-1.4.2-218.ipa -d /tmp/verify ls -l /tmp/verify/Payload/MyApp.app/MyApp # 必须是 -rwxr-xr-x

结构不对的包,后面三层再对也没用,所以这层最先做。unzip -l的输出如果第一层不是Payload/,直接回去重新打包,不用往下看了。

# 第 2 层:签名验证 codesign -dv --verbose=4 /tmp/verify/Payload/MyApp.app 2>&1 | grep -E "Identifier|TeamIdentifier|Authority" # Identifier 要和 bundle id 一致,Authority 要是你的证书名 # 严格校验,能通过说明签名链完整 codesign --verify --deep --strict --verbose=2 /tmp/verify/Payload/MyApp.app # 期望输出:valid on disk / satisfies its Designated Requirement

-dv是显示签名详情,--verbose=4让输出包含完整证书链。--verify这一层用--deep是合理的,因为这里是校验不是签名,目的是把嵌套内容一起查一遍。

# 第 3 层:描述文件与权限的一致性 security cms -D -i /tmp/verify/Payload/MyApp.app/embedded.mobileprovision > /tmp/verify-profile.plist plutil -extract ExpirationDate raw /tmp/verify-profile.plist # 日期必须晚于当前时间,过期的描述文件签出来的包一律装不上 plutil -extract Entitlements.application-identifier raw /tmp/verify-profile.plist plutil -extract CFBundleIdentifier raw /tmp/verify/Payload/MyApp.app/Info.plist # 两个值的后缀部分必须一致

描述文件过期是最容易被忽略的一种失效:签名本身完全正确,codesign --verify也能过,但设备在校验描述文件有效期时直接拒绝安装。内测包放了几个月才发出去,十有八九栽在这里。

# 第 4 层:真机安装,两种方式选一种 ideviceinstaller -i ./dist/MyApp-1.4.2-218.ipa # Xcode 15 之后的替代方案,先解出 .app 再装 xcrun devicectl list devices xcrun devicectl device install app --device <UDID> /tmp/verify/Payload/MyApp.app

ideviceinstaller -i全程在终端里跑,输出直接能看到失败码,适合脚本化。devicectl是 Xcode 15 之后新加的设备管理入口,list devices能列出当前连接的设备和 UDID,--device后面跟那个 UDID。两种方式装完都去设备桌面上找图标,确认不是灰色的,点开跑一遍主流程,能正常进到首页才算真的通了。

那次把一个过期三个月的描述文件签出来的包发给整个测试组,二十几台设备全部装不上,我在群里挨个解释的场面至今记得。从那以后我每次打包完都强制走一遍这四层验证,尤其是第三层的过期时间,哪怕当天就要发出去也照跑不误,多花两分钟总比让人家白装一遍强。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询