主题
XHIM iOS/macOS 共享实现与发行维护
本目录不是客户接入入口,只保存 iOS 与 macOS 共同使用的 Swift Facade、 SwiftUI 组件、XCFramework 组包和签名工具。两个平台的公开产品文档、示例和 验收互相独立。
第一次在空白工程接入? iOS 请先看 iOS 从零快速接入;使用 CocoaPods 请直接看 iOS CocoaPods 从零接入;macOS 请看 macOS 从零快速接入。本页只面向 XHIM SDK 发行、 签名、安全和高级二次开发人员,不是客户端首次接入教程。服务端部署另见 XHIM Server 文档。 登录设备查询、踢设备、自撤销与只读策略 API 见 iOS QuickStart 或 macOS QuickStart 的“登录设备管理”。
2026-07-25 实测状态:仓库内 Apple 原生 Product Adapter 已使用
URLSessionHTTP(S)/WS(S)、生产态系统SecTrust、Endpoint Bundle Ed25519 校验和 Keychain 账号数据库 Key 接入统一 C++ Engine。iOS Device arm64 与 Simulator arm64/x86_64 的 SQLCipher 4.12.0 Development XCFramework、二进制 Swift Package 和真实服务端 Demo 均已完成构建;公司开发证书签名的 Demo 已安装到 登记 iPhone。模拟器 Bob 已通过局域网 Development 通道完成真实send → 服务端提交 → sync → ServerAccepted,Alice/Bob 发送、同步和已读 闭环已通过。 这仍是受控 Development 证据,不等于面向客户的 Stable 商业发布。 2026-07-26 已把正式流水线收紧为 iOS Device、iOS Simulator、macOS arm64/x86_64 三个平台 Slice,并增加二进制 SHA-256、内嵌构建来源、 完整 ABI baseline、Mach-O Platform/Minimum OS 和空白消费者门禁;由于旧 Development 制品没有 macOS slice,它只是历史联调证据,必须用受审 SQLCipher 与 Product Adapter 重新构建后才能进入新门禁。本机逐项证据见 Apple iOS 接入验证记录。
1. 商用交付边界:先区分三种制品
| 制品 | 用途 | 网络 | 数据库 | 可对外售卖 |
|---|---|---|---|---|
preview | ABI/Wrapper 和本地流程测试 | 不连接 XHIM Server | 编译进 SQLCipher,但不注入 Key | 否 |
development | 真实服务器、模拟器和登记真机联调 | 固定 Apple Product Adapter | SQLCipher + Keychain 随机 Key | 仅受控测试 |
production | 客户正式登录、收发、同步和推送 | 固定且审计过的 Apple Product Adapter | SQLCipher + Keychain 随机 Key,缺失即失败 | 完成全部正式门禁后 |
.localPreview 只用于自动化。development 和 production 均关闭 Local Preview、要求数据库加密并嵌入真实 Adapter;production 另外强制:
- Git 工作区干净;
XHIM_ENABLE_LOCAL_PREVIEW=OFF;XHIM_REQUIRE_DATABASE_ENCRYPTION=ON;- 提供公司签名身份;
- 显式提供经审核的 Privacy Manifest;
- iOS Device、Simulator 与 macOS 均包含同一版本的 C ABI 和 SQLCipher;
- Build Manifest 的制品 SHA-256、Commit、Toolchain、Product Adapter、 Local Preview 和数据库加密声明全部复核通过。
Apple 客户最终只依赖 Swift Package 或 CocoaPods,不编译 C++ 内核或管理 C handle。iOS 首发交付目录为:
text
XHIMSwift/
├── Package.swift
├── XHIM.podspec
├── XHIMSwiftUI.podspec
├── XHIMCore.xcframework C ABI + Product Adapter + SQLCipher
├── XHIMCore.build-manifest.json 最终签名目录树与内嵌来源绑定
├── Sources/XHIM/ Swift Headless Facade
├── Sources/XHIMSwiftUI/ SwiftUI 组件,可选
├── Sources/XHIM/Resources/ PrivacyInfo.xcprivacy
├── Examples/XHIMDemo/ 可运行 Xcode Demo
├── QUICKSTART_IOS.md iOS 空白工程教程
├── QUICKSTART_COCOAPODS_IOS.md iOS CocoaPods 空白工程教程
├── QUICKSTART_MACOS.md macOS 空白工程教程
├── UI_ASSETS.md UI 资源版本与许可证说明
├── LICENSE
├── NOTICE
└── RELEASE-MANIFEST.jsonXHIMCore.xcframework 至少包含:
- iOS Device arm64;
- iOS Simulator arm64 和当前支持的 Intel Simulator slice;
- macOS arm64/x86_64,或按发布支持矩阵拆分;
- 模块化 C 头
xhim/xhim_v1.h; - 真实 Product Adapter,正式包关闭 Local Preview;
- 同版本的符号文件、UUID、Commit SHA、ABI/Wire/DB Schema 版本。
当前核心是静态库 XCFramework,内部是 libXHIMCore.a,并不存在 .framework 子目录。签名单位是 XCFramework 外层 Bundle:签名前写入 XHIMBuildProvenance.json,再用一次 codesign 把 Info.plist、三组静态库、 Headers 和来源声明一起封装。禁止遍历并“签名每个 framework”,因为那会在 静态库布局下得到空集合,形成假通过或必失败门禁。
Apple 官方支持把 XCFramework 作为 Swift Package 的 binary target 分发,并用 checksum 校验远端 ZIP。参考: 创建 XCFramework、 通过 Swift Package 分发二进制 Framework。
2. 当前公司 Apple 资源
- Team:
Bazhou Xihan Software Technology Co., Ltd(FG4922H85G); - Demo Bundle ID:
com.xihansoftware.xhim.demo; - App ID 已启用 Push Notifications;
- 开发描述文件:
XHIM Demo iOS Development; - XCFramework 签名:
Apple Distribution: Bazhou Xihan Software Technology Co., Ltd (FG4922H85G); - 测试 iPhone 已登记在该 Team。
证书私钥只保存在登录 Keychain,仓库只记录 identity 名称,不保存 .p12、私钥、 描述文件或 APNs 私钥。APNs Auth Key 属于服务端 Secret,不能放入 SDK、Demo、 App Bundle 或 Git。
3. 构建 Apple Development 包
3.1 构建 SQLCipher Apple Slice
使用经过法务和安全审查的 SQLCipher 4.12.0 amalgamation。社区版需要遵守其 许可证与用户可见归属要求;正式销售也可替换为 Zetetic 商业二进制,不能把本仓库 脚本当作商业授权。
bash
scripts/apple/build_sqlcipher_ios.sh \
--source-dir /secure/vendor/sqlcipher-4.12.0-amalgamation \
--output-dir build-ios-delivery/sqlcipher脚本固定使用 Apple CommonCrypto,输出 iPhone arm64、Simulator arm64/x86_64 和 macOS arm64/x86_64,并记录源码 SHA-256、Xcode 版本及 iOS/macOS 最低版本。它会验证 sqlite3_key_v2、sqlcipher_version 等符号,拒绝普通 SQLite。
3.2 接收服务端联调配置
Apple SDK 发行人员不负责部署 XHIM Server。开始 Development 制品验证前,由 服务端负责人按照 交付给客户端团队的联调信息 提供可访问的 Server URL、测试账号和测试会话。
发行流水线如需把公开 Endpoint 配置嵌入 Product Adapter,应从受保护的 CI 配置读取;服务端数据库、容器命令、管理凭证和签名私钥不能进入 Apple 构建 脚本或交付包。Development 明文网络只允许受控测试制品,Production 必须使用 系统可信 HTTPS/WSS,不能绕过 SecTrust。
3.3 构建并签名 Development XCFramework
Development 与 Production 使用仓库内同一 Apple Adapter;区别是 Development 允许脏工作区和测试 Endpoint,不能发给客户:
bash
set -a
source /secure/xhim/apple-development-public.env
set +a
export XHIM_PRODUCT_ADAPTER_SOURCE_DIR="$PWD/adapters/apple_server"
export XHIM_PRODUCT_ADAPTER_OBJECT_TARGET=xhim_apple_product_adapter
scripts/apple/build_ios_xcframework.sh \
--mode development \
--sdk-version 0.1.0-beta.1 \
--sqlcipher-dir build-ios-delivery/sqlcipher \
--output-dir build-ios-development/apple \
--signing-identity \
"Apple Distribution: Bazhou Xihan Software Technology Co., Ltd (FG4922H85G)" \
--expected-team-id FG4922H85Gapple-development-public.env 由发行 CI 管理,只允许包含 Product Adapter 所需 的公开 Endpoint 参数;它不是服务端运行环境文件,也不包含数据库或管理凭证。
产物:
text
build-ios-development/apple/
├── XHIMCore-development.xcframework
└── XHIMCore-development.manifest.json签名验证:
bash
scripts/apple/verify_ios_xcframework.sh \
--artifact build-ios-development/apple/XHIMCore-development.xcframework \
--build-manifest build-ios-development/apple/XHIMCore-development.manifest.json \
--mode development \
--require-macos \
--require-signature \
--expected-team-id FG4922H85G3.4 组装二进制 Swift Package 与 CocoaPods 包
bash
scripts/apple/assemble_ios_package.sh \
--mode development \
--artifact build-ios-development/apple/XHIMCore-development.xcframework \
--build-manifest build-ios-development/apple/XHIMCore-development.manifest.json \
--sqlcipher-license /secure/vendor/sqlcipher-4.12.0/LICENSE.md \
--sdk-version 0.1.0-beta.1 \
--expected-team-id FG4922H85G \
--output-dir build-ios-development/distribution脚本会生成本地二进制 Swift Package/CocoaPods 包、XCFramework ZIP、SwiftPM checksum、CocoaPods source ZIP/SHA-256、私有 Specs 目录、内部 RELEASE-MANIFEST.json、外部 Release Attestation、LICENSE、NOTICE、 SQLCipher/Monocypher 许可证归属和 Xcode Demo。它会在无证书条件下构建空白 SwiftPM/CocoaPods 的 iOS 与 macOS 消费者;没有匹配的 SQLCipher 许可证或 Build Manifest 时拒绝组包。
bash
open \
build-ios-development/distribution/XHIMSwift-development/Examples/XHIMDemo/XHIMDemo.xcodeproj选择 XHIMDemo Scheme。真机确认 Signing 使用 FG4922H85G Team 和 XHIM Demo iOS Development。如果仓库位于 iCloud/File Provider 目录,真机 构建应把 DerivedData 放到普通本地目录,例如 -derivedDataPath /tmp/xhim-derived-device,避免 Finder 元数据污染 .app。
源码仓库中的 Demo 引用相邻源码 Package;面向客户验证应打开上述组装目录中的 Demo,这样依赖的是最终二进制 XCFramework,而不是本机临时 CMake Library。
3.5 Alice/Bob 双端互通
- iPhone 打开“晞晗IM Demo”,输入 Server 局域网地址和
alice; - 模拟器输入同一个 Server 地址和
bob; - 两端都显示
ready后,Alice 发送一条消息; - Bob 应在一次 WSS Hint + Sync 后看到该消息并回复;
- Alice 应看到 Bob 回复;服务端
xhim_messages.server_sequence必须单调递增, 客户端本地状态必须进入serverAccepted,不能只以“已进入 Outbox”判成功。
自动化启动时只需注入 Server 地址和账号。变量只存在于本次启动命令,不会写入 工程:
bash
XHIM_TEST_SERVER_URL="https://im-test.customer.com"
# Simulator 启动 Bob
SIMCTL_CHILD_XHIM_DEMO_ACCOUNT=bob \
SIMCTL_CHILD_XHIM_DEMO_SERVER_URL="$XHIM_TEST_SERVER_URL" \
xcrun simctl launch booted com.xihansoftware.xhim.demo
# 已解锁的登记真机启动 Alice;把 <CoreDevice-ID> 换成 devicectl 中的值
DEVICECTL_CHILD_XHIM_DEMO_ACCOUNT=alice \
DEVICECTL_CHILD_XHIM_DEMO_SERVER_URL="$XHIM_TEST_SERVER_URL" \
xcrun devicectl device process launch \
--terminate-existing \
--device <CoreDevice-ID> \
com.xihansoftware.xhim.demoApple 侧验收状态、事件和时间线;消息是否已经持久化、账号成员关系和服务端审计 由服务端负责人按照 Server 文档独立验收,不在 Apple 工程中执行数据库命令。
Demo 已通过 projectionEvents 在投影提交后重新查询时间线,不再每秒轮询。 生产 App 同样由账号级 ViewModel/Repository 持有订阅,并只调用 SDK 查询 API, 不要直接读取 SQLite。
4. 客户项目接入
4.1 CocoaPods
本地试用包:
ruby
pod 'XHIM', :path => '../XHIMSwift-development'
pod 'XHIMSwiftUI', :path => '../XHIMSwift-development'本轮生成的受控 Development 快照在上传并登记私有 Specs 后按下面方式精确锁定:
ruby
source 'git@git.xihansoftware.com:laowang/xhim-specs.git'
source 'https://cdn.cocoapods.org/'
pod 'XHIM', '= 0.1.0-dev.2'
pod 'XHIMSwiftUI', '= 0.1.0-dev.2'该版本不是 Stable 销售制品;CocoaPods 的 ~> 0.1 不会选择 prerelease。 登记完成前使用发行负责人提供的同版本本地包。
正式版本由发行人员上传脚本生成的 *.pod-source.zip,再把 CocoaPodsSpecs-production/Specs 中的两个版本目录提交到私有 Specs 仓库。 完整发布顺序见 CocoaPods 私有发行。客户只需:
ruby
source 'git@git.xihansoftware.com:laowang/xhim-specs.git'
source 'https://cdn.cocoapods.org/'
pod 'XHIM', '~> 0.1'
pod 'XHIMSwiftUI', '~> 0.1'只有首个通过商业门禁的稳定 0.1.x Podspec 存在后,上述稳定版本范围才有效。
详细步骤见 iOS CocoaPods 从零接入。
4.2 Swift Package Manager
内测阶段可在 Xcode 使用 File → Add Package Dependencies → Add Local... 选择组装后的 XHIMSwift-development 目录。App Target 必选 XHIM;需要基础 会话/聊天视图时再选 XHIMSwiftUI。
正式发布后,客户通过 Xcode 的 Package Dependencies 添加仓库地址,或在 Package.swift 中声明依赖。发行仓库内部使用类似配置:
swift
// 这是发行包 manifest 示例,不代表当前仓库已经产出该 URL 和 checksum。
.binaryTarget(
name: "XHIMCore",
url: "https://<your-cdn>/xhim/<version>/XHIMCore.xcframework.zip",
checksum: "<swift-package-compute-checksum>"
)App Target 只链接 Headless XHIM;需要当前基础页面时再增加 XHIMSwiftUI。未来的 UIKit 包也必须保持可选,不能成为登录、收发、同步或 媒体能力的前置依赖。
最低系统版本、Xcode/Swift 版本、模拟器架构和是否支持 Mac Catalyst 必须写入 每个版本的发布清单,不能只写“支持 iOS/macOS”。
业务 App 的推荐入口会自动发现部署、创建账号数据库、启动并登录:
swift
let client = try await XHIMClient.connect(
server: "https://im.customer.com",
userID: account.userID,
authentication: .business {
try await AccountAPI.current.xhimCredential()
}
)Development Server 开启 Easy Login 时省略 authentication 即可。底层显式 XHIMClient(configuration:)、start、login 和 updateCredential 继续 保留,供迁移、发行验证和高级账号容器使用,不是空白项目的推荐路径。
.localPreview 不认证、不联网,只能用于开发包:
swift
let preview = try XHIMClient(configuration: .init(
appID: "com.xihansoftware.xhim.demo",
storageURL: previewDatabaseURL,
mode: .localPreview
))5. 构建 Production 包
仓库内 adapters/apple_server 是 Production 构建使用的 Apple Product Adapter。它负责:
- 通过
URLSession和受系统SecTrust管理的 HTTPS/WSS 完成认证、 Endpoint 校验、收发和增量同步; - 从 Keychain 创建/读取数据库路径隔离的 32 字节随机 Key,使用
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,缺失或读取失败即 fail-closed; - 把 Session Backend、Message Transport 和 Database Key Provider 作为同一个 Build Product Adapter 返回;
- 使用
DeploymentConfig提供的客户 Endpoint 签名公钥,并固定 HTTPS/WSS scheme、帧与响应体上限、超时、取消、Epoch/Generation fence 和错误映射;客户域名只通过验签且未过期的 Endpoint Bundle 发现,同一 XCFramework 不需要因部署地址变化而重编; - 使用 Monocypher 4.0.3 的 Ed25519 校验 Endpoint Bundle,避免依赖 Apple SDK 未公开的 Ed25519 Security.framework API;
- 缺 Token、Keychain Key、系统信任链或服务端配置时 fail-closed。
正式售卖前仍必须完成客户生产域名/KMS 配置、Keychain rekey/备份恢复矩阵、 APNs、正式隐私清单、全设备矩阵和发布门禁;Development 构建和一次真机联调 不能替代这些证据。
Production 组包还必须显式传入经隐私/法务复核的 --privacy-manifest /secure/reviewed/PrivacyInfo.xcprivacy。仓库自带的空收集 清单只描述不联网的 Preview,不能原样用于会上传账号标识、消息或媒体的生产包。
bash
set -a
source /secure/release/xhim-apple-client.env
set +a
export XHIM_PRODUCT_ADAPTER_SOURCE_DIR="$PWD/adapters/apple_server"
export XHIM_PRODUCT_ADAPTER_OBJECT_TARGET=xhim_apple_product_adapter
scripts/apple/build_ios_xcframework.sh \
--mode production \
--sdk-version 0.1.0 \
--sqlcipher-dir /secure/build/sqlcipher-ios \
--output-dir /secure/final/apple \
--signing-identity \
"Apple Distribution: Bazhou Xihan Software Technology Co., Ltd (FG4922H85G)" \
--expected-team-id FG4922H85G
scripts/apple/assemble_ios_package.sh \
--mode production \
--artifact /secure/final/apple/XHIMCore-production.xcframework \
--build-manifest /secure/final/apple/XHIMCore-production.manifest.json \
--privacy-manifest /secure/reviewed/PrivacyInfo.xcprivacy \
--privacy-review-id PRIVACY-2026-001 \
--legal-review-id LEGAL-2026-001 \
--sqlcipher-license /secure/vendor/sqlcipher-4.12.0/LICENSE.md \
--sdk-version 0.1.0 \
--cocoapods-source-url \
"https://sdk.xihansoftware.com/xhim/swift/0.1.0/XHIMSwift-0.1.0-production.pod-source.zip" \
--expected-team-id FG4922H85G \
--output-dir /secure/final/distribution
scripts/release/stage_apple_packages.sh \
0.1.0 \
/secure/final/distribution \
/secure/final-artifacts \
FG4922H85Gadapters/reference_server 仍只用于桌面/服务端纵向联调,不能替代 adapters/apple_server。Production 构建要求干净 Git 工作区、正式签名、 数据库加密和显式 Product Adapter;任何缺项都应失败,不能改成明文、 Local Preview 或全信任 TLS 兜底。stage_apple_packages.sh 会把 Package、 私有 Specs、XCFramework ZIP、CocoaPods source ZIP 和 Release Attestation 原子复制到统一 apple/ 制品目录并复核全部 Hash。Apple 签名只允许发生在 build_ios_xcframework.sh;跨平台 sign_artifacts.sh 对 Apple 只验签, 绝不能在组包后重签并让 SwiftPM/CocoaPods 校验和失效。
6. 当前源码验证
macOS 主机仍可验证 C++ 内核和 CMake 安装包:
bash
cmake -S /path/to/xhim -B /path/to/build-xhim-apple \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DXHIM_BUILD_TESTS=ON \
-DXHIM_WARNINGS_AS_ERRORS=ON
cmake --build /path/to/build-xhim-apple
ctest --test-dir /path/to/build-xhim-apple --output-on-failure这条命令验证源码内核,不替代上述 Apple XCFramework、Swift Package、Demo、 签名和真机链路。新流水线会分别为 iOS Device、iOS Simulator 与 macOS 构建 目标文件,再由 xcodebuild -create-xcframework 组装;只允许同一平台的 arm64/x86_64 合并为 Universal Archive,不能把 iOS 与 macOS 目标文件直接 lipo 成伪平台 Slice。
每个 slice 都必须使用一致的 XHIM Commit、Product Adapter、C++ 标准库策略、 编译开关和协议版本。不要把 Device 与 Simulator 的目标文件直接用 lipo 伪装为一个平台 slice。
Apple 门禁自身的快速回归:
bash
python3 -m unittest discover \
-s scripts/apple/tests -p 'test_*.py' -v
bash -n scripts/apple/*.sh它只验证 Attestation/Manifest 的拒绝逻辑和脚本语法;正式发布仍必须运行完整 XCFramework 构建及两个空白消费者脚本。
7. Swift Facade API
推荐由一个 actor 或内部串行执行器持有唯一 Client handle:
swift
// 实际入口位于 Sources/XHIM/XHIMClient.swift。
let policy = XHIMRuntimePolicy(
requestTimeoutMilliseconds: 15_000,
syncPageSize: 200,
reconnectInitialDelayMilliseconds: 1_000,
reconnectMaxDelayMilliseconds: 30_000,
maxReconnectAttempts: 8
)
public final class XHIMClient {
public static func connect(
server: String,
userID: String,
appID: String? = nil,
authentication: XHIMAuthentication = .development,
storageRootURL: URL? = nil
) async throws -> XHIMClient
public static func connect(
serverURL: URL,
userID: String,
appID: String? = nil,
authentication: XHIMAuthentication = .development,
storageRootURL: URL? = nil
) async throws -> XHIMClient
public func start() async throws
public func login(accountHint: String, accessToken: String) async throws
public func updateCredential(accessToken: String) async throws
public func logout() async throws
public func sendText(
conversationID: String,
clientMessageID: String?,
text: String
) async throws -> XHIMSendReceipt
public func sendMessage(
conversationID: String,
clientMessageID: String? = nil,
message: XHIMOutgoingMessage
) async throws -> XHIMSendReceipt
public func retryMessage(
clientMessageID: String
) async throws -> XHIMSendReceipt
public func cancelMessage(
clientMessageID: String
) async throws -> XHIMSendReceipt
public func message(
clientMessageID: String
) async throws -> XHIMMessage
public func messages(
conversationID: String,
cursor: Data? = nil,
limit: Int = 50
) async throws -> XHIMMessagePage
public func conversations(
cursor: Data? = nil,
limit: Int = 50
) async throws -> XHIMConversationPage
public func markConversationRead(
conversationID: String,
throughServerSequence: Int64
) async throws -> XHIMConversationReadReceipt
public func shutdown() async
}XHIMRuntimePolicy 和 XHIMDeployment 会在 client_create 时复制并固定; 不要根据一次请求临时修改全局超时。客户可提供 Bootstrap URL、Endpoint Key ID 和 Ed25519 公钥;实际 API/WSS/上传/下载地址只能来自验签且未过期的 Endpoint Bundle。TLS Trust 仍由正式 Product Adapter fail-closed 管理,业务 不能关闭校验。
源码消费验证:
bash
swift build --package-path platforms/apple \
-Xlinker -L -Xlinker /path/to/xhim-native-lib业务初始化可直接参考 Examples/QuickStart.swift。SwiftUI 组件位于 Sources/XHIMSwiftUI,通过数组和动作闭包接入,不直接读取 SQLite。
事件流使用 AsyncStream<XHIMEvent> 或同等原生机制。Facade 公开 Swift struct/enum/Error,不公开 UnsafeMutablePointer<xhim_v1_client_t>、C callback、 SQLite 路径细节或 C++ 类型。
持久数据刷新使用独立的 AsyncStream<XHIMProjectionChange>client.projectionEvents。messageUpserted/messageStateChanged/ conversationChanged/socialChanged/syncApplied 只表示本地权威投影已经失效, 不是业务事件日志;订阅后先查询一次,之后按 scope/ID 重新调用 SDK。未知 kind/schema 会成为 projectionInvalidated,必须做宽范围重查。所有 C 借用内存 均在回调返回前复制,SwiftUI 可使用 MainActor XHIMProjectionRequeryController,UI 不直接访问 SQLite。
7.1 已读调用
messages 返回最新消息在前的 XHIMMessagePage,翻页时只能把 nextCursor 原样传回,不能解析、拼接或跨账号/会话复用。聊天页只有在一批 消息已经完成布局并对用户可见后,才能取这批消息中最大的正 serverSequence 调用:
swift
let page = try await client.messages(conversationID: conversationID)
let highestVisibleServerSequence = page.messages
.compactMap(\.serverSequence)
.max()
guard let highestVisibleServerSequence else { return }
let receipt = try await client.markConversationRead(
conversationID: conversationID,
throughServerSequence: highestVisibleServerSequence
)
// receipt.unreadCount 是服务端回包落库后的值,不是 UI 本地推算值。本地 Pending/Sending 消息没有服务端序号,必须排除。调用完成前不要把角标 乐观清零;使用返回的 receipt 更新当前页面。监听 .conversationReadChanged(let conversationID) 时重新读取该会话摘要,它也会在 同账号其他设备推进已读后出现。同值或更低值允许重复调用并返回 no-op。
conversations 返回置顶优先、随后按持久活动排序的摘要页;收到 .conversationReadChanged 后用该 API 重新拉取对应摘要。业务层不得自己打开 SQLite、用数组下标或设备时间制造序号。
实时 Presence/Typing(非持久投影)
publishPresence 和 publishTyping 复用已登录会话,业务层不传 Credential。 服务端权威回显包含 sequence 与 expiresAtMilliseconds;对端从 events 接收 presenceChanged/typingChanged。这两类状态不写 SQLite,也不进入 projectionEvents,UI 必须到期自动清除。输入状态只发布开始/停止转换,禁止 按每个按键上报;完整调用示例见 QuickStart 的“在线状态与正在输入”。
7.2 自定义消息与 UI Renderer
产品自定义消息使用自己的稳定命名空间和版本,例如 com.xihan.product-card@1。发送前先由业务插件校验,再把同一个不可变信封交给 Client:
swift
let plugins = XHIMMessagePluginRegistry()
try plugins.register(ProductCardPlugin())
let outgoing = XHIMOutgoingMessage(
contentType: "com.xihan.product-card",
contentVersion: 1,
payload: cardJSON,
fallbackText: "[商品卡片]"
)
try plugins.validate(outgoing)
try await client.sendMessage(
conversationID: conversationID,
message: outgoing
)拉取消息后调用 plugins.presentation(for:) 生成会话摘要和通知摘要。未知 contentType、插件不支持的新版本、Payload 校验失败或插件抛错时,Registry 统一返回 fallbackText;原始 Data 仍由 Core 保存和分页,不能因为 UI 不认识而丢弃或阻塞 Cursor。
若插件返回 rendererKey,可在 XHIMMessageRendererRegistry 注册 XHIMMessageItemRenderer。Renderer 只接收复制后的 XHIMMessage 和展示摘要; 失败时退回基础文本气泡。不要在插件或 Renderer 中读取 SQLite、持有 C handle, 也不要用 Renderer 结果替换消息协议真相。完整示例见 Examples/QuickStart.swift。
8. C ABI Bridge 规则
- 创建所有 C 结构体前清零,填写
struct_size和XHIM_V1_ABI_VERSION; - Swift 字符串显式编码为 UTF-8 byte buffer;不能把字符数量当字节长度;
- Callback 里的
xhim_v1_bytes_view_t、消息数组和 Cursor 在返回前深拷贝为Data/String; - C Callback 不在 MainActor。复制完成后再用受控 continuation、Actor 或
MainActor.run投递; - 每个异步请求只恢复 continuation 一次;同步拒绝时立即释放 callback context;
- Swift
Task.cancel()会调用 nativecancel_request;收到request_cancelled后只结束该 Task,不假定已提交消息或已读事务被回滚; XHIMNativeError完整复制domain/stableCode/nativeCode/retryable/retryAfterMilliseconds/userAction/operationID/traceID;业务不解析 message;- 收到
.credentialRequired后只调用updateCredential,不要销毁数据库或用 account hint 替换服务端确认的 canonical account; cancelMessage只取消尚未进入发送中的本地 Outbox,不等于服务端撤回;markConversationRead只提交 XHIM 时间线中已展示消息的服务端序号;等待 receipt 后再更新未读,不在 Swift 层维护第二份 read sequence;- Event subscription 与 Client 分开持有,先取消订阅,再 shutdown/destroy;
client_destroy必须与全部 Client 调用串行化。不要依赖 Swiftdeinit完成正常关闭;deinit只做泄漏兜底;- Callback 内可以发起新请求,但不能阻塞等待另一个 XHIM Callback。
9. 生命周期和应用状态
建议由 App 的账号/会话容器拥有 XHIMClient,而不是由某个 ViewController 或 SwiftUI View 创建:
text
App/Scene 启动 → create/start
账号登录 → login,等待 Ready
进入后台 → 保持 Core;按策略暂停附件任务,不销毁数据库
账号退出 → logout,清理账号级 UI 状态
进程终止 → shutdown,再释放 Wrapper多 Scene 共享一个账号 Client。不同账号使用不同 Client 和数据库目录,不能让 多个 Scene 或 Extension 同时打开同一 profile。Notification Service Extension 也不能直接复用主 App 数据库;通知解码应使用单独、明确版本化的轻量组件。
10. 存储和安全
- 数据库放在 Application Support 下的账号隔离目录,不放 Bundle、Documents 公共导出区或 Caches;
- 数据库的
-wal、-shm和媒体临时文件执行一致的数据保护与备份策略; - 若后台推送后需要在首次解锁之后访问数据,应由产品安全评审选择合适的 Data Protection 等级,不能为了后台方便直接关闭保护;
- Token 和数据库/媒体密钥进入 Keychain,不进入 UserDefaults、日志或 Crash 附件;正式包把随机数据库密钥注入 SQLCipher Product Adapter;
- storage profile 目录名使用不透明 ID,不直接使用手机号、邮箱或 Token;
- App Group、Extension 和多进程访问必须单独设计,当前版本不支持共享连接。
11. 附件消息与可选实时音视频
XHIMAttachmentPickerButton基于 PHPicker/UIImagePicker/DocumentPicker/ NSOpenPanel,返回沙盒可读 URL;sendImage/sendVideo/sendAudio/sendFile默认创建 Core 持久化上传任务; 后台计算 SHA-256、鉴权、断点续传、校验和依赖消息入队由 Core 负责;- API 返回只表示 durable admission。事件只给可查询的 task hint,完整状态通过
mediaTask(taskID:)获取;cancelMediaTask与取消一次 SwiftTask的 通用 request cancellation 语义不同; acceptMediaDownload使用稳定MediaRef和扁平cacheKey创建持久化 下载。不得从storagePath推导缓存路径,也不得把服务器文件名当 cache key;- 下载完成后只通过
XHIMMediaCacheReader异步读取已校验字节;open/读取在 I/O 队列,单 Reader 串行且可晚于 Client 关闭,停止 chunk 循环即取消; - Apple Reference/Product Adapter 未提供安全 cache reader 时,open 明确返回
media_cache_unsupported,不会退化为路径拼接; - task、event、
diagnostics()和网络 Payload 都不包含本地路径、临时 URL、 bearer token 或签名 URL;diagnostics 是同步、只读、纯内存快照; notifyNetworkAvailable()仅把 NWPathMonitor 的“网络恢复”转成同步 best-effort 提示,无 callback/request ID;Core 重连定时器继续作为保底;XHIMServerMediaUploader/XHIMMediaUploading仅作为旧版兼容和定制迁移 接口,不是默认商用接入路径;- 实时音视频不属于纯 IM 首发包的 Stable 门禁。后续接入厂商时使用独立可选
XHIMCallKitBridge和版本化 Provider SPI; - CallKit、PushKit、AVAudioSession、蓝牙路由和厂商 RTC 对象不得进入消息 Payload,也不得穿过稳定 C ABI。
12. UI Kit 与二次开发
建议拆分:
text
XHIM Headless Swift Facade
XHIMUIKit UIKit 会话/聊天/联系人/媒体组件
XHIMSwiftUI SwiftUI 包装与页面
XHIMCallKitBridge 系统通话桥,可选UI Kit 通过协议注入主题、头像/图片加载、国际化、路由、菜单动作和自定义消息 Renderer。业务自定义 Cell 只读取平台消息模型,不读取 SQLite、不持有 C handle。
当前 XHIMSwiftUI 已内置同一套可商用 Lucide 矢量图标和 XHIMTheme。业务可直接使用 XHIMIconView(.send)、XHIMIconView(.paperclip),也可通过 .environment(\.xhimTheme, customTheme) 覆盖颜色。资源来源、固定 Commit、 SHA-256 和许可证见 五端 UI 资源说明。
13. 发布验收
- 空白消费者分别只通过最终 Swift Package、CocoaPods 完成 iOS Device/Simulator 与 macOS 构建,SDK 验证阶段关闭 App 签名;
- 发布者另行完成空白 macOS App arm64/x86_64 的 Developer ID/公证验证;
xhim_v1_abi_version()与 Wrapper 编译时常量一致;- Instruments/Thread Sanitizer 下无 callback context、subscription 或 handle 泄漏;
- 前后台、Scene 重建、锁屏、网络切换、进程重启和账号切换通过真机测试;
- XCFramework ZIP checksum、外层 Bundle 签名、Build Manifest/Release Attestation、Privacy Manifest、符号文件、LICENSE、NOTICE 和 SBOM 齐全;
- UI Kit 删除后 Headless SDK 仍可登录、收发和分页查询。
14. OfflineReader 平台契约
XHIMOfflineReader 是 iOS/macOS 共用的只读 facade,直接覆盖当前 C ABI 的消息、 消息搜索、会话以及好友申请/好友/群/群成员/黑名单/入群申请六类分页。
open、查询和close全部在私有 utility queue 执行;- 每次 C 调用返回前把 page、嵌套字符串、payload 和 opaque cursor 深拷贝为 Swift 值对象;
- 句柄只在该串行队列访问,
close()幂等,关闭后返回offline_reader_closed; - 数据库 Key 只能由
XHIMOfflineDatabaseKeyProvider在运行时注入。SDK 清理 临时Data,不得把 Key 写进代码、plist、UserDefaults、日志或崩溃附件; - 未知 status 保留原始
code;消息和社交未知枚举同时保留native*值。
业务接入代码见 iOS 和 macOS。OfflineReader 不负责建库、 迁移、登录或网络同步,传入的必须是 Core 已创建且账号绑定一致的当前 Schema 数据库。