补记说明:本文于 2026 年 10 月 25 日整理并首次发布。页面日期是这份经验的归档日期,历史案例与当前开放状态分别说明。

咫尺是我正在做的一款双人互动 App。写它的 iPhone 版时,我想把能共用的代码留在一个包里。后来开始做原生 Mac 版和 Apple Watch 配套版,我才更具体地理解了“跨平台”到底要跨什么。

这篇先从一个简单问题说起:既然芯片有相似之处,为什么不能把同一份软件包到处用?

一个很容易误判的词,是 arm64,它描述处理器理解和执行指令的一种架构。iPhone 上有它,Apple 芯片 Mac 上有它,Mac 里的 iPhone 模拟器——用电脑模拟手机环境的开发工具——也可以有它。但只看这几个字,仍然回答不了一个库能不能链接、一个 App 能不能启动。

我现在查构建问题,会先拆开看:处理器架构、目标平台与运行环境、最低系统、包里的依赖。 最后再回到实际设备验证。

01 / 代码要去哪里运行
架构相似,也要为不同目的地分别编译 iPhone 真机、手机模拟器、原生 Mac 和手表属于不同编译目标。arm64 字样不能独自确定兼容。 先问给谁用,再看有没有对应的那一份 iPhone 真机 iOS · arm64 Mac 里的手机模拟器 iOS Simulator · arm64 原生 Mac App macOS · arm64 / x86_64 这次检查的手表目标 watchOS · arm64_32
编译目标对照示意。卡片里的架构是正文所讨论的目标,并非咫尺已通过全部设备验收的支持名单。

芯片有相似之处,软件仍有不同的目的地#

拿几个常见目标作对照:

目标可能看到的架构仍要分清什么
iPhone 真机arm64给 iOS 设备编译的二进制
Apple 芯片 Mac 上的 iPhone 模拟器arm64给 iOS Simulator 编译的二进制
原生 Mac Apparm64、x86_64macOS 平台,以及实际包含哪些架构
咫尺 CI 选取的手表真机目标arm64_32watchOS 平台,以及 32 位的整数宽度

这张表是解释构建目标,不是咫尺已经通过所有设备验收的兼容名单。

Apple 的架构排错文档专门指出,真机与模拟器的二进制不能随意混合;跨平台库应该用分别编译的版本组成 XCFramework。XCFramework 可以理解成一个按平台、运行环境和架构整理好的库包,构建时从中选合适的一份。Apple:处理 Apple 芯片上的架构构建错误、创建多平台二进制框架

因此,依赖报错时,我会继续问:这个 arm64 是给真机、模拟器,还是 macOS 的?只查到“文件里有 arm64”还没查完。

手表上的一个坑:同样的数字,未必装得下#

咫尺的共用逻辑里,有金额、消息序号、授权修订号、时间和小游戏种子。它们在 iPhone 上工作,并不能证明在手表目标上也合适。

代码里常用 Int 存整数。但这个类型能装多大的数,与编译目标有关。接入 Apple Watch 时,编译暴露了一个具体问题:arm64_32 目标里的 Int 只有 32 位。我们把需要固定宽度的金额、序号等字段改成了 Int64,也修正了一个随机种子边界。

下面是简化后的写法对照:

// 在 32 位 Int 上,计算这个上界就会出问题。
let oldRange = 0 ..< (1 << 31)

// 如果需要的是非负 Int32 范围,可以明确写出边界。
let range = 0 ... Int(Int32.max)

这里最值得记住的不是“所有 Int 都换成 Int64”。数组下标和许多系统 API 仍然使用 Int。需要先判断:这个值是当前进程里的索引,还是跨设备保存、从服务器传来、需要稳定范围的数据?后者更值得把宽度写明白。

当时的改动还核对了 JSON、CloudKit 和既有游戏夹具,避免修完手表编译,把手机上的数据约定一起改坏。对应构建和检查有记录;没有记录的设备行为,仍不能顺手算作通过。

02 / 数字也要考虑设备差异
需要稳定范围的数据,把整数宽度写明白 手表目标的 Int 是 32 位,金额和序号等跨设备数据按需要改为固定宽度;不能把所有 Int 一律替换。 这次手表目标:Int 为 32 位 整数范围比我原先预想的小 共用的金额、序号等数据 需要时明确使用 Int64 先看数据含义,再选整数范围 数组下标等仍按系统接口使用 Int
整数宽度示意。箭头表示遇到问题后的处理方向;它不表示所有手表都相同,也不是“把所有整数都改成 Int64”的建议。

新工具能做出来,还要看旧系统能不能用#

另一个容易混在一起的,是“用哪一套开发工具”和“最低支持哪个系统”。SDK 是开发时使用的系统工具与接口集合;用较新的 SDK,不等于用户都得换成最新系统。

咫尺用较新的 Xcode 和 SDK 构建,但把 iPhone 的最低系统设为 iOS 17。要做到这一点,不能只改主 App 的一行配置:小组件、扩展、本地包和打进去的动态框架,都要一起看。

项目现在的兼容检查会打开实际软件包,核对 Info.plist(包里的配置文件)和 Mach-O(编译后的程序文件)中记录的最低系统,并检查较新框架的链接方式。代码里用到新 API 的地方,再按系统能力提供降级。这比只看工程设置更接近用户最终拿到的东西。

例如,某个能力需要更高版本系统,就应该给旧系统留出清楚的替代或不可用状态,不能因为 App 安装得上,就让按钮看起来什么都能做。Apple:在特定平台或系统版本上运行代码

不过,这类不运行 App、只检查软件包的静态检查,加上编译,也只能覆盖其中一部分。旧 iPhone 上的启动、权限、声音、通知和实际操作,仍要拿对应系统的设备测。写“最低支持 iOS 17”,和写“iOS 17 真机已验收”,是两条不同的记录。

平时检查可以少做一些,发给别人之前要看完整范围#

设备多起来之后,把代码变成软件包所需的时间也会增长。CI 是自动运行构建和检查的流程。咫尺的一次 CI 调整,把模拟器和 Mac 的日常检查收窄到 arm64:当时承担这些任务的机器都是 Apple 芯片,没必要每次顺带生成一份 x86_64。

手表检查却刻意保留 arm64_32,因为它能暴露前面那类 32 位问题。少做重复工作,和保留能发现新问题的检查,是可以一起考虑的。

这份架构收窄配置只给 CI 用,正式打包不读取它。发布前仍要检查产物:主程序和每个原生依赖到底带了什么,目标系统是什么。

如果想支持 Intel Mac,仅在主程序里加上 x86_64 还不够,依赖也要有对应版本。Apple 对 Universal macOS 二进制的说明,讲的也是同一个 macOS 程序中包含 arm64 和 x86_64 两份代码;这不意味着它能变成 iPhone 包。Apple:构建 Universal macOS 二进制

咫尺 CI 在 Apple 芯片上通过,也不能据此宣布 Intel Mac 的实际使用已经验收。

下次遇到“这个包不行”,先把问题问具体#

我会先记下设备型号与芯片、系统版本、安装包的版本和来源,再区分卡在下载、安装、启动,还是某个功能。涉及原生库,再核对它的目标平台、架构和最低系统。

为了不被一个“不能用”带跑,我会先把表格填到能确定下一步的程度:

失败位置优先核对什么这一步还不能证明什么
下载不到渠道、版本可见范围、链接是否有效不能直接判断芯片不支持
编译或链接失败库给哪个平台和环境编译、实际架构不能靠文件名判断一定兼容
安装失败系统版本、分发渠道、应用身份与签名不能只看主程序有 arm64
启动就退出实际故障构建、依赖、崩溃与匹配符号不能把别台设备的成功当作复验
只有某个功能失效权限、系统能力、该端是否提供这项功能不能把整个 App 都归因于架构

例如,手机模拟器里的库不能用于真机时,应回到库的编译目的地;手表上的数字越界,应回到数据类型;旧系统上的按钮没有结果,则还要看有没有新接口可用性判断和清楚的降级提示。

这些只是排查方向,不是看到某个症状就已经确定根因。最好让每次修改都对应一条能够重复的验证:换对库以后重新链接,改整数范围以后检查数据兼容,补降级以后再去对应系统看实际行为。

如果没有目标设备,我会保留“未做真机验证”这一格。提前把支持范围说窄一些,比把所有平台都画进兼容清单、等用户报错后再解释,更容易维护信任。

这能把一个很大的“适配问题”,拆成有方向的小问题:包拿错了、依赖缺一份、最低系统不匹配,或者平台本来就没有那项能力。每一种,处理办法都不同。

这篇整理的是咫尺已有的工程改动和检查做法,没有在写作时重新构建或执行设备验收。我的收获是:复用代码能少做一部分工作,目标平台的差异仍然要逐项留下答案。

咫尺各端的当前入口、最低系统与已知限制,见产品公开说明。文中的工程检查不扩大这份公开支持范围。