精简你的 App Clip
Streamline your App Clip
2020年6月25日
一句话判断
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。