本章覆盖 iOS 开发的最后一公里:SwiftUI 渲染性能优化、ARC 内存管理、Instruments 性能分析、XCTest 单元/UI 测试、TestFlight 内测发布和 App Store 发布流程。
11.1 SwiftUI 渲染性能优化
视图重建原理
swift
复制代码
// SwiftUI 重建规则:
// 当 @State / @Observable / @Binding 变化时,持有这些状态的 View.body 重新执行
// 子视图:若参数(让 Equatable)没有变化,则不重建
// ❌ 不良写法:所有子视图随父视图重建
struct PoorPerformanceView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("计数:\(counter)")
Button("增加") { counter += 1 }
// 每次 counter 变化,以下全部重建(即使数据没变)
ExpensiveListView(data: staticData) // 不必要的重建!
ComplexGridView(items: staticItems) // 不必要的重建!
}
}
}
// ✅ 拆分子视图:独立状态,精准更新
struct BetterPerformanceView: View {
var body: some View {
VStack {
CounterSectionView() // 只有这个视图有 @State
ExpensiveListView(data: staticData) // 不受 counter 影响
ComplexGridView(items: staticItems) // 不受 counter 影响
}
}
}
struct CounterSectionView: View {
@State private var counter = 0
var body: some View {
Text("计数:\(counter)")
Button("增加") { counter += 1 }
}
}
使用 Equatable
swift
复制代码
// 遵循 Equatable + .equatable() 防止不必要重建
struct ArticleCard: View, Equatable {
let article: Article
// 只比较 id 和 isBookmarked,减少不必要重建
static func == (lhs: ArticleCard, rhs: ArticleCard) -> Bool {
lhs.article.id == rhs.article.id
&& lhs.article.isBookmarked == rhs.article.isBookmarked
}
var body: some View {
// 复杂视图内容...
}
}
// 在父视图中使用
ArticleCard(article: article).equatable()
LazyStack 与 List
swift
复制代码
// ❌ Stack(把所有视图一次性渲染)
ScrollView {
VStack {
ForEach(0..<1000) { index in
HeavyRowView(index: index) // 1000个全部创建!
}
}
}
// ✅ LazyVStack(只渲染可见区域)
ScrollView {
LazyVStack(spacing: 8) {
ForEach(0..<1000) { index in
HeavyRowView(index: index) // 按需创建,滚动时回收
}
}
}
// ✅ List(更高性能,支持滑动交互)
List(items) { item in
ItemRow(item: item)
}
// ✅ 图片使用 drawingGroup(复杂视图合并渲染)
ComplexView()
.drawingGroup() // 离屏渲染为单个位图,减少 GPU 合成层
避免频繁创建视图
swift
复制代码
// ❌ 在 body 中创建闭包/对象(每次渲染都创建)
struct BadView: View {
var body: some View {
let formatter = DateFormatter() // 每次 body 执行都创建!
formatter.dateStyle = .medium
return Text(formatter.string(from: Date()))
}
}
// ✅ 在类型层面定义静态实例
struct GoodView: View {
private static let dateFormatter: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .medium
return f
}()
var body: some View {
Text(Self.dateFormatter.string(from: Date()))
}
}
11.2 内存管理
ARC 与循环引用
swift
复制代码
// ARC:自动引用计数,引用计数 = 0 时释放内存
// ❌ 循环引用(内存泄漏)
class Owner {
var pet: Pet?
deinit { print("Owner 释放") }
}
class Pet {
var owner: Owner? // 强引用 → 循环!
deinit { print("Pet 释放") }
}
var owner: Owner? = Owner()
var pet: Pet? = Pet()
owner?.pet = pet
pet?.owner = owner
owner = nil // Owner 不会释放!
pet = nil // Pet 也不会释放!
// ✅ 使用 weak 打破循环
class Pet {
weak var owner: Owner? // 弱引用:不增加引用计数
deinit { print("Pet 释放") }
}
// ✅ ViewModel 中的异步任务
@Observable
class DataViewModel {
var items: [Item] = []
func load() {
Task { [weak self] in // 避免 Task 延长 self 生命周期
guard let self else { return }
let items = try? await DataService.shared.fetchItems()
self.items = items ?? []
}
}
}
Instruments 内存分析
复制代码
步骤:
1. Xcode → Product → Profile(⌘I)
2. 选择 "Leaks" 模板
3. 运行 App,操作各种功能
4. 观察:
- Leaks:检测"红色!"= 内存泄漏点
- Allocations:跟踪内存分配,找峰值
- VM Tracker:查看虚拟内存占用
常见泄漏原因:
① 循环引用(class A → class B → class A)
② 闭包强捕获 self(忘记 [weak self])
③ Timer 未 invalidate
④ NotificationCenter 观察者未移除(Swift 可自动处理)
⑤ Delegate 用 strong 而非 weak
11.3 启动性能优化
swift
复制代码
// 测量启动时间:Xcode → Product → Profile → "App Launch"
// ① App 入口轻量化
@main
struct iOSDemosApp: App {
init() {
// ✅ 只做必要的同步初始化(< 400ms)
AppConfig.configure()
Logger.general.info("App 启动")
// ❌ 避免在 init() 中做:
// - 网络请求
// - 大文件读取
// - 复杂数据库初始化
// - 第三方 SDK 的重型初始化
}
var body: some Scene {
WindowGroup {
ContentView()
.task {
// ② App 显示后,并行初始化其他模块
await AppInitializer.shared.initialize()
}
}
}
}
// ② 并行初始化(减少总等待时间)
actor AppInitializer {
static let shared = AppInitializer()
private var initialized = false
func initialize() async {
guard !initialized else { return }
initialized = true
// 三个初始化任务并行执行
async let authTask: Void = AuthManager.shared.restoreSession()
async let configTask: Void = RemoteConfig.shared.fetch()
async let analyticsTask: Void = AnalyticsManager.shared.setup()
_ = await (authTask, configTask, analyticsTask)
}
}
// ③ 懒加载重型资源
class ResourceManager {
// 不在初始化时加载,首次访问时才加载
lazy var largeDataSet: [ComplexModel] = {
loadLargeDataSet()
}()
}
11.4 XCTest 单元测试
swift
复制代码
import XCTest
@testable import iOS_demos // 访问内部类型
// ViewModel 单元测试
@MainActor
final class ArticleViewModelTests: XCTestCase {
var viewModel: ArticleListViewModel!
var mockRepository: MockArticleRepository!
override func setUp() async throws {
mockRepository = MockArticleRepository()
viewModel = ArticleListViewModel(repository: mockRepository)
}
override func tearDown() async throws {
viewModel = nil
mockRepository = nil
}
// 测试:加载成功
func testLoadArticlesSuccess() async throws {
mockRepository.mockArticles = Article.mocks(count: 10)
await viewModel.loadFirstPage()
XCTAssertFalse(viewModel.isLoading, "加载完成后 isLoading 应为 false")
XCTAssertNil(viewModel.errorMessage, "成功时不应有错误信息")
XCTAssertEqual(viewModel.articles.count, 10)
XCTAssertEqual(viewModel.articles.first?.title, "Article 1")
}
// 测试:加载失败
func testLoadArticlesFailure() async {
mockRepository.shouldThrowError = NetworkError.serverError
await viewModel.loadFirstPage()
XCTAssertFalse(viewModel.isLoading)
XCTAssertNotNil(viewModel.errorMessage)
XCTAssertTrue(viewModel.articles.isEmpty)
}
// 测试:搜索过滤
func testSearchFiltering() async {
mockRepository.mockArticles = [
Article(title: "Swift 入门"),
Article(title: "SwiftUI 进阶"),
Article(title: "Objective-C 基础"),
]
await viewModel.loadFirstPage()
viewModel.searchText = "Swift"
XCTAssertEqual(viewModel.filteredArticles.count, 2)
XCTAssertTrue(viewModel.filteredArticles.allSatisfy {
$0.title.contains("Swift")
})
}
// 测试:异步加载更多
func testLoadMore() async {
mockRepository.mockArticles = Article.mocks(count: 20)
await viewModel.loadFirstPage()
mockRepository.mockArticles = Article.mocks(count: 10)
await viewModel.loadMore()
XCTAssertEqual(viewModel.articles.count, 30)
XCTAssertEqual(viewModel.currentPage, 2)
}
// 性能测试
func testLoadingPerformance() {
measure {
let exp = expectation(description: "loading")
Task {
await viewModel.loadFirstPage()
exp.fulfill()
}
wait(for: [exp], timeout: 5)
}
}
}
// Mock 依赖
final class MockArticleRepository: ArticleRepositoryProtocol {
var mockArticles: [ArticleEntity] = []
var shouldThrowError: Error?
var searchCallCount = 0
func fetchAll(page: Int, pageSize: Int) async throws -> [ArticleEntity] {
if let error = shouldThrowError { throw error }
return mockArticles
}
func fetchById(_ id: String) async throws -> ArticleEntity {
guard let article = mockArticles.first(where: { $0.id == id }) else {
throw NetworkError.notFound
}
return article
}
func bookmark(_ id: String) async throws {}
func search(query: String) async throws -> [ArticleEntity] {
searchCallCount += 1
return mockArticles.filter { $0.title.contains(query) }
}
}
11.5 UI 测试(XCUITest)
swift
复制代码
final class LoginUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["--uitesting"]
app.launchEnvironment = ["MOCK_API": "1"]
app.launch()
}
func testLoginFlow() throws {
// 验证初始状态
XCTAssertTrue(app.navigationBars["登录"].exists)
XCTAssertFalse(app.buttons["login_button"].isEnabled)
// 输入用户名
let usernameField = app.textFields["username_field"]
XCTAssertTrue(usernameField.exists)
usernameField.tap()
usernameField.typeText("test@example.com")
// 输入密码
let passwordField = app.secureTextFields["password_field"]
passwordField.tap()
passwordField.typeText("password123")
// 验证按钮可用
XCTAssertTrue(app.buttons["login_button"].isEnabled)
// 点击登录
app.buttons["login_button"].tap()
// 等待首页出现(最多 10 秒)
let homeNav = app.navigationBars["首页"]
XCTAssertTrue(homeNav.waitForExistence(timeout: 10))
}
// 截图记录
func testScreenshot() {
let screenshot = XCTAttachment(screenshot: app.screenshot())
screenshot.name = "登录页面"
screenshot.lifetime = .keepAlways
add(screenshot)
}
}
// View 中添加 accessibilityIdentifier(供 UI 测试定位)
TextField("邮箱", text: $email)
.accessibilityIdentifier("username_field")
SecureField("密码", text: $password)
.accessibilityIdentifier("password_field")
Button("登录") { login() }
.accessibilityIdentifier("login_button")
11.6 TestFlight 与 App Store 发布
发布前检查清单
markdown
复制代码
代码质量:
□ 所有单元测试通过(⌘U)
□ 无编译警告
□ 无内存泄漏(Instruments 检测)
配置检查:
□ 版本号(CFBundleShortVersionString)格式:x.y.z
□ Build 号(CFBundleVersion)每次上传递增
□ Bundle ID 与 App Store Connect 一致
□ 签名证书有效(Distribution Certificate)
□ Provisioning Profile 包含 App Store
资源检查:
□ App 图标 1024×1024(PNG, 无 Alpha 通道)
□ 所有尺寸启动页正常
□ 国际化字符串完整
隐私合规:
□ Info.plist 权限描述准确
□ App Privacy Report 中隐私标签与实际一致
□ 不得含有私有 API
构建发布:
□ Product → Archive
□ Validate App(本地验证)
□ Distribute App → App Store Connect
□ TestFlight 内测(邀请测试人员)
□ App Store 提交审核
版本管理自动化
bash
复制代码
# 使用 fastlane 自动化发布流程
# 安装:gem install fastlane
# Fastfile 配置
lane :beta do
increment_build_number # 自动递增 build 号
build_app(scheme: "iOS_demos") # 编译
upload_to_testflight # 上传到 TestFlight
slack(message: "新版本已上传 TestFlight!")
end
lane :release do
increment_version_number(bump_type: "minor")
build_app(scheme: "iOS_demos")
upload_to_app_store(
skip_metadata: false,
skip_screenshots: false,
submit_for_review: true,
automatic_release: false # 手动发布
)
end
章节总结
| 主题 |
关键技术 |
重要程度 |
| 视图性能 |
拆分子视图/Equatable/LazyStack |
⭐⭐⭐⭐⭐ |
| 内存管理 |
weak/unowned/[weak self] |
⭐⭐⭐⭐⭐ |
| 性能分析 |
Instruments Leaks/Allocations |
⭐⭐⭐⭐ |
| 启动优化 |
轻量 init/并行初始化 |
⭐⭐⭐⭐ |
| 单元测试 |
XCTest + Mock 依赖注入 |
⭐⭐⭐⭐⭐ |
| UI 测试 |
XCUITest + accessibilityIdentifier |
⭐⭐⭐⭐ |
| 发布流程 |
Archive → TestFlight → App Store |
⭐⭐⭐⭐⭐ |
Demo 说明
| 文件 |
演示内容 |
PerformanceDemo.swift |
视图重建对比 / LazyStack vs VStack |
MemoryLeakDemo.swift |
循环引用检测与修复 |
XCTestDemo.swift |
单元测试 + Mock 完整示例 |
UITestDemo.swift |
登录流程 UI 测试 |
📎 扩展内容补充
来源:第十一章_进阶原理.md
本章概述:深入理解 Swift 内存模型(ARC 底层原理)、Swift 现代并发模型(async/await/Actor/Sendable)、Core Animation 渲染原理,以及 Swift 5.9 宏(Macros)。
11.1 Swift 内存模型与 ARC 原理
概念讲解
ARC 底层工作原理
swift
复制代码
// ARC 为每个对象维护一个引用计数
// 对象创建:引用计数 = 1
// 赋给新变量:引用计数 +1
// 超出作用域/设为 nil:引用计数 -1
// 引用计数 = 0:释放内存,调用 deinit
class Node {
var value: Int
var next: Node? // 默认是 strong
init(_ value: Int) {
self.value = value
print("Node(\(value)) 创建,内存地址:\(Unmanaged.passUnretained(self).toOpaque())")
}
deinit {
print("Node(\(value)) 释放")
}
}
// 引用计数追踪
var a: Node? = Node(1) // 引用计数 = 1
var b = a // 引用计数 = 2
a = nil // 引用计数 = 1
b = nil // 引用计数 = 0 → 释放
// 值类型无需 ARC
struct Point { var x, y: Double }
// Stack 上分配,无引用计数开销
let p1 = Point(x: 1, y: 2)
let p2 = p1 // 深拷贝,完全独立
Copy-on-Write(写时复制)
swift
复制代码
// Swift 的集合类型(Array/Dictionary/Set)采用 COW
// 相同数据共享内存,修改时才实际复制
var array1 = [1, 2, 3, 4, 5]
var array2 = array1 // 未发生实际复制!共享同一块内存
array2.append(6) // 此时才发生实际复制(Copy-on-Write)
// 自定义 COW
struct MyBuffer {
private var storage: StorageClass // 引用类型的存储
mutating func modify() {
if !isKnownUniquelyReferenced(&storage) {
storage = StorageClass(copying: storage) // 复制
}
storage.data.append(0)
}
}
11.2 Swift 现代并发模型
概念讲解
async/await 底层原理
swift
复制代码
// async 函数在 Swift 运行时中是"可挂起函数"
// 遇到 await 时,保存执行上下文,释放线程
// 等待完成后,恢复执行(可能在不同线程)
// 协作式调度(Cooperative Scheduling)
async func demonstrateSuspension() {
print("1: 开始") // 主线程
let result = await someAsyncOperation() // 挂起,让出线程
print("2: 恢复,结果:\(result)") // 可能在不同线程恢复
}
// 任务优先级
Task(priority: .high) { ... }
Task(priority: .medium) { ... } // 默认
Task(priority: .low) { ... }
Task(priority: .background) { ... }
Task(priority: .utility) { ... }
// 结构化并发 - 子任务跟随父任务
func parentTask() async {
async let child1 = subtask1() // 启动子任务
async let child2 = subtask2() // 启动子任务
let results = await (child1, child2) // 等待所有子任务
// 如果父任务被取消,子任务自动取消
}
Sendable - 跨并发域安全
swift
复制代码
// Sendable 标记类型可以安全跨越 Actor/Task 边界
struct SafeMessage: Sendable {
let id: UUID
let content: String // 不可变值类型,天然 Sendable
}
// Class 需要手动保证线程安全才能 Sendable
final class UserInfo: Sendable {
let name: String // let + 不可变 = 安全
let id: UUID
init(name: String, id: UUID = UUID()) {
self.name = name
self.id = id
}
}
// Actor - 隔离可变状态(自动 Sendable)
actor SafeCounter {
private var value = 0
func increment() { value += 1 }
func getValue() -> Int { value }
// nonisolated - 不需要隔离的方法,可同步调用
nonisolated var description: String {
"Counter"
}
}
全局 Actor
swift
复制代码
// @MainActor - 主线程 Actor(类比 Dart 的主 Isolate)
@MainActor
class UIUpdater {
var status: String = ""
func update(status: String) {
self.status = status // 总在主线程执行
}
}
// 自定义全局 Actor
@globalActor
actor DatabaseActor {
static let shared = DatabaseActor()
}
@DatabaseActor
func writeToDatabase(data: Data) async {
// 总在 DatabaseActor 上执行,保证数据库访问串行
}
11.3 Core Animation 渲染原理
概念讲解
复制代码
iOS 渲染流程(类比 Flutter 的 Build→Layout→Paint→Composite):
CPU 阶段:
1. 视图树更新(UIView/CALayer 属性变化)
2. 布局计算(layoutSubviews)
3. 显示准备(drawRect/绘制指令)
GPU 阶段:
4. 提交到 Core Animation 渲染服务器
5. 纹理合成(Compositor)
6. Metal/OpenGL 渲染
7. 推送到显示屏(60/120fps)
优化关键点:
- 避免离屏渲染(cornerRadius + clipsToBounds 会触发离屏渲染)
- 使用 CALayer.shouldRasterize 缓存复杂图层
- 减少透明图层叠加(混合)
swift
复制代码
// 检测离屏渲染(Instruments → Core Animation)
// ✅ 避免触发离屏渲染的写法
layer.cornerRadius = 12
layer.masksToBounds = true
// ❌ 更高效但外观相同
// 在 Assets.xcassets 中直接提供带圆角的图片
// CALayer 直接操作(比 UIView 更底层、更高性能)
class AnimatedLayer: CALayer {
override func draw(in ctx: CGContext) {
ctx.setFillColor(UIColor.blue.cgColor)
ctx.fillEllipse(in: bounds)
}
}
// 显示链接(精准的帧动画)
class GameView: UIView {
private var displayLink: CADisplayLink?
func startAnimation() {
displayLink = CADisplayLink(target: self,
selector: #selector(tick))
displayLink?.preferredFrameRateRange =
CAFrameRateRange(minimum: 60, maximum: 120, preferred: 120)
displayLink?.add(to: .main, forMode: .common)
}
@objc func tick(displayLink: CADisplayLink) {
// 每帧调用,精准控制动画
let timestamp = displayLink.timestamp
// 更新游戏逻辑...
}
}
11.4 Swift Macros(Swift 5.9+ 宏)
概念讲解
swift
复制代码
// Swift 宏在编译期展开,类比 C 的宏但类型安全
// @Observable 就是一个宏
// 使用宏(类比 Java 的注解处理器 / Kotlin 的 ksp)
@Model // SwiftData 模型宏
class Task { ... }
@Observable // 替代 ObservableObject + @Published
class ViewModel { ... }
// 常见内置宏
#Preview { ... } // 预览宏
#warning("待处理") // 编译警告
#error("不能这样用") // 编译错误
#line, #column, #function, #file // 调试信息
#if ... #else ... #endif // 条件编译
// stringify 宏示例(来自官方示例)
@freestanding(expression)
macro stringify<T>(_ value: T) -> (T, String) =
#externalMacro(module: "MacrosMacros", type: "StringifyMacro")
// 使用
let (result, code) = #stringify(1 + 2)
print(result) // 3
print(code) // "1 + 2"
// 实际项目中常用的宏
// - @CasePathable (TCA):自动生成 case path
// - @ObservableState (TCA):结合 @Observable 的状态
// - @DependencyClient:依赖注入
章节总结
| 进阶主题 |
核心概念 |
重要程度 |
| ARC 原理 |
引用计数/COW/循环引用 |
⭐⭐⭐⭐⭐ |
| async/await原理 |
可挂起函数/协作调度 |
⭐⭐⭐⭐⭐ |
| Actor |
并发隔离/线程安全 |
⭐⭐⭐⭐⭐ |
| Sendable |
跨并发域类型安全 |
⭐⭐⭐⭐ |
| Core Animation |
渲染流程/性能优化 |
⭐⭐⭐⭐ |
| Swift Macros |
编译期代码生成 |
⭐⭐⭐ |
Demo 说明
| Demo 文件 |
演示内容 |
MemoryModelDemo.swift |
ARC 引用计数/循环引用可视化 |
ConcurrencyDemo.swift |
async/await/Actor/TaskGroup 演示 |
CoreAnimationDemo.swift |
CALayer/显示链接/性能分析 |
SwiftMacrosDemo.swift |
@Observable/@Model 宏展示 |