Streamline your App Clip
App Store & Distribution 进阶 24m

精简你的 App Clip

Streamline your App Clip

2020年6月25日

在 Apple 官方观看视频

一句话判断

App Clip 的 10MB 体积限制不是一个约束,而是一个设计哲学——它迫使你只保留最核心的功能。这个 Session 教你如何在体积限制内打造最流畅的即时体验。

这场 Session 讲了什么

Session 10118 讲了”为其他企业创建 App Clips”的商业模式,这个 Session 则聚焦在技术实现层面——如何让你的 App Clip 体积尽可能小、启动尽可能快、体验尽可能好。

Apple 详细讲解了 App Clip 的体积优化策略。10MB 限制是压缩后的下载大小(thin binary),不是未压缩的二进制大小。影响体积的主要因素包括:第三方库的大小、Asset Catalog 中的图片资源、多语言支持、以及不必要的架构。Session 还介绍了如何使用 Xcode 的 App Size Report 来精确分析你的 App Clip 的体积构成,以及如何通过配置 Build Settings 来减小最终产物。

值得深挖的点

依赖精简策略

你的主 App 可能依赖了 10 个第三方库,但 App Clip 只需要其中 2 个。通过 Target Membership 配置,你可以让 App Clip 只链接它真正需要的库。更进一步,你可以为 App Clip 创建一个更轻量的替代实现。比如主 App 使用 Alamofire 做网络请求,App Clip 可以直接用 URLSession——省掉了第三方库的体积。

启动时间优化

App Clip 的启动时间直接影响用户体验。Apple 建议将 App Clip 的启动分为三个阶段:系统加载(你无法控制)、App 初始化(尽早完成)、首屏渲染(越快越好)。关键优化包括:延迟加载非首屏必需的模块、使用轻量级的首屏 UI、避免在启动时做网络请求。

代码片段

配置 App Clip 的 Target Membership

场景:确保 App Clip 只包含必要的源文件和资源。

// App Clip 应该只包含核心功能的源文件
// 在 Xcode 的 Target Membership 中:
// - 主 App Target:所有文件
// - App Clip Target:只有核心功能文件

// 网络层精简版(App Clip 专用)
// 不依赖第三方网络库,直接使用 URLSession
class ClipNetworkService {
    static let shared = ClipNetworkService()
    private let session = URLSession.shared
    
    func fetch<T: Decodable>(url: URL,
                               type: T.Type) async throws -> T {
        let (data, response) = try await session.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw ClipError.invalidResponse
        }
        
        return try JSONDecoder().decode(T.self, from: data)
    }
}

优化首屏加载

场景:App Clip 启动后立即展示核心内容。

import SwiftUI

@main
struct RestaurantClipApp: App {
    var body: some Scene {
        WindowGroup {
            // 首屏:直接显示加载状态
            ClipRootView()
        }
    }
}

struct ClipRootView: View {
    @StateObject private var viewModel = ClipViewModel()
    
    var body: some View {
        Group {
            switch viewModel.state {
            case .loading:
                // 轻量级的加载界面,不要使用复杂的动画
                VStack(spacing: 16) {
                    ProgressView()
                    Text("正在加载菜单...")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
                
            case .loaded(let menu):
                // 核心内容:菜单列表
                MenuView(menu: menu)
                
            case .error(let message):
                ErrorView(message: message)
            }
        }
        .onAppear {
            // 启动后立即开始加载数据
            viewModel.loadMenu()
        }
    }
}

分析和减小 Asset 体积

场景:优化图片资源以符合 10MB 限制。

// Asset Catalog 优化策略:

// 1. 使用 PDF 矢量图替代 @2x/@3x 位图
// 在 Asset Catalog 中:
// - Render As: Template Image(单色图标)
// - Scales: Single Scale(矢量图只需一份)

// 2. 压缩大图
// 使用 `assetutil` 命令行工具分析 Asset Catalog:
// $ assetutil --info Assets.car

// 3. 只保留 App Clip 需要的图片
// 创建独立的 Asset Catalog 给 App Clip Target

// 4. 使用 SF Symbols 替代自定义图标
// SF Symbols 不占额外体积,系统内置
struct IconView: View {
    var body: some View {
        // 使用系统图标,体积为零
        Image(systemName: "cart.fill")
            .font(.title)
            .foregroundColor(.blue)
    }
}

Build Settings 优化

场景:为 App Clip Target 配置最优的编译设置。

// 在 App Clip Target 的 Build Settings 中:

// 1. 只保留 arm64 架构(App Clip 不需要模拟器支持)
// Architectures: arm64

// 2. 优化编译选项
// Optimization Level: -O (Fast, Whole Module Optimization)
// Strip Debug Symbols During Copy: YES
// Strip Swift Symbols: YES
// Make Strings Read-Only: YES

// 3. 死代码剥离
// Dead Code Stripping: YES

// 4. 精简语言支持
// 只保留 App Clip 需要的语言
// 在 Project -> Info -> Localizations 中移除不必要的语言

生成 App Size Report

场景:在 Xcode 中分析 App Clip 的体积构成。

// 步骤:
// 1. 在 Xcode 中选择 App Clip Target
// 2. Product -> Archive
// 3. 在 Organizer 中选择 "Distribute App"
// 4. 选择 "Development" 或 "Ad Hoc" 分布
// 5. 勾选 "App Thinning: All compatible device variants"
// 6. 导出后查看 App Thinning Size Report.txt

// 报告示例:
// App Clip Size Report
// --------------------
// App + On Demand Resources: 8.2 MB (compressed)
//   App: 6.1 MB
//   On Demand Resources: 2.1 MB
//
// Universal Variant: 8.2 MB
// arm64 Variant: 7.8 MB
//
// 主要构成:
// - Executable: 3.2 MB
// - Assets.car: 2.8 MB
// - Frameworks: 1.5 MB
// - Other: 0.3 MB

最佳实践

已有项目:用 App Size Report 分析你的主 App 体积,评估哪些部分可以精简到 App Clip 中。先创建一个最小化的 App Clip(只有核心功能),在满足 10MB 限制后再逐步添加功能。对于超出体积的部分,考虑使用 On-Demand Resources 来延迟加载。

新项目:从架构设计阶段就把”核心功能 vs 扩展功能”的边界画清楚。核心功能放入 App Clip Target,扩展功能只保留在主 App 中。使用 Protocol 和抽象层来隔离功能模块,让同一套代码可以在精简模式和完整模式之间切换。

还有什么值得关注

  • App Clip 的 10MB 限制只计算下载大小,运行时解压后的内存占用不受此限制。
  • On-Demand Resources 可以为 App Clip 额外提供最多 200MB 的延迟加载资源。
  • Xcode 12 新增了 App Clip 专用的模板项目,自动配置好了 Target 和 Entitlement。
WWDC 2020