Build light and fast App Clips
App Store & Distribution 进阶 24m

构建轻量快速的 App Clips

Build light and fast App Clips

2021年6月10日

在 Apple 官方观看视频

一句话判断

10MB 限制不是建议,是生死线——如果你的 App Clip 超过这个大小,用户在蜂窝网络下的体验会灾难性地差,这场 Session 教你怎么把体积压到极限。

这场 Session 讲了什么

App Clips 的核心卖点就是「快」:用户扫码/碰 NFC 后,几秒钟内就能用上你的服务。但「快」的前提是包体足够小。iOS 15 中 App Clip 的最大限制仍然是 10MB(展开后),这场 Session 聚焦在如何在这个限制内构建功能完整的体验。

Session 从三个层面讲解瘦身策略:编译期裁剪、资源优化和架构设计。编译期方面,Xcode 13 新增了 App Clip 大小报告,构建完成后会直接显示包体大小明细。资源方面,建议用 Asset Catalog 的 app-clip tag 来标记 App Clip 专用资源,避免把完整 app 的资源全部打包进去。

架构层面,Session 引入了一个重要观点:App Clip 不应该是完整 app 的精简版,而应该是一个独立的功能切片。这意味着你应该按使用场景设计 App Clip 的代码结构,而不是做减法。

值得深挖的点

动态库 vs 静态链接的体积权衡

App Clip 默认使用静态链接来消除动态库的加载开销,但静态链接会把所有用到的符号都编入二进制。如果你的 App Clip 只用了一个庞大 SDK 的 10% 功能,整个 SDK 都会被链接进来。Session 建议对第三方依赖做模块化拆分:只引入你需要的子模块,而不是整个 SDK。对于 Swift Package Manager 管理的依赖,可以用 .product(name:package:) 精确引用。

资源按需加载策略

图片和本地化文件是包体膨胀的主要来源。Session 展示了一个技巧:App Clip 只包含核心 UI 资源,其他内容通过按需下载(On-Demand Resources)在运行时获取。比如一个停车 App Clip,核心缴费界面打包在 10MB 内,但停车场地图数据在用户进入后通过 ODR 下载。这样既满足了大小限制,又不牺牲功能完整性。

代码片段

// 使用 Asset Catalog 的 app-clip tag 区分资源
// 在 Asset Catalog 中,给 App Clip 专用图片添加 "app-clip" tag
// 完整 app 专用图片不添加此 tag

// Xcode 会自动在 App Clip target 中排除无 tag 的资源
// 按需加载非核心资源
import OnDemandResources

class ResourceLoader {
    let resourceRequest: NSBundleResourceRequest
    
    init(tags: Set<String>) {
        resourceRequest = NSBundleResourceRequest(tags: tags)
    }
    
    func loadMapData() async throws -> Data {
        // 请求 ODR 资源
        try await resourceRequest.beginAccessingResources()
        defer { resourceRequest.endAccessingResources() }
        
        guard let url = Bundle.main.url(
            forResource: "parking_map",
            withExtension: "json"
        ) else {
            throw ResourceError.notFound
        }
        return try Data(contentsOf: url)
    }
}

enum ResourceError: Error {
    case notFound
}
// 检查 App Clip 包体大小(构建脚本)
// 在 Build Phase 中添加 Run Script:
// xcodebuild -scheme "AppClip" -destination 'generic/platform=iOS'
//   archive -archivePath build.xcarchive
//   OTHER_CODE_SIGN_FLAGS=--generate-entitlement-der
//
// 构建后在 Report Navigator 中查看 App Clip Size Report

最佳实践

在 CI 流水线中加入包体大小检查。如果 App Clip 的包体超过 8MB(留 2MB 余量),自动构建失败。可以用 xcrun --sdk iphoneos size 命令在构建后获取精确大小。

对于 SwiftUI 视图,避免在 App Clip 中使用大量的 SF Symbols 变体。每个 SF Symbol 的渲染模板都会占用空间,如果只用到标准变体,就不要引入 filled、circle 等变体。

还有什么值得关注

  • Xcode 13 的 Organizer 中新增了 App Clip 的安装转化率指标,可以看到有多少用户从 App Clip 跳转到完整 app。
  • Swift Concurrency 在 App Clip 中完全可用,async/await 不会增加额外的运行时体积。
  • 如果你的 App Clip 使用 Core ML 模型,考虑把模型放在服务端通过 API 调用,一个 5MB 的 mlmodel 文件会直接吃掉一半的预算。
WWDC 2021