本章深入讲解 Swift 结构化并发体系:async/await 基础、Task 生命周期管理、并行执行(async let / TaskGroup)、Actor 并发安全、@MainActor 主线程约束,以及 Combine 响应式编程。
8.1 async/await 基础
async/await 是 Swift 5.5 引入的结构化并发语法,让异步代码像同步代码一样清晰。
swift
// 定义异步函数(可挂起,不阻塞线程)
func fetchUser(id: String) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpRes = response as? HTTPURLResponse,
(200...299).contains(httpRes.statusCode) else {
throw NetworkError.serverError
}
return try JSONDecoder().decode(User.self, from: data)
}
// 调用异步函数
Task {
do {
let user = try await fetchUser(id: "123")
print("用户:\(user.name)")
} catch {
print("错误:\(error)")
}
}
// await 的工作原理
// 1. 函数在 await 处挂起,让出当前线程
// 2. 等待操作完成(可能在其他线程)
// 3. 恢复执行(可能在不同线程,@MainActor 除外)
8.2 Task - 异步任务管理
swift
// Task 是结构化并发的基本单元
// ① 在 SwiftUI 生命周期中启动 Task
struct DataView: View {
@State private var data: [Item] = []
var body: some View {
List(data) { ItemRow(item: $0) }
// .task 修饰符:视图出现时启动,消失时自动取消
.task {
await loadData()
}
}
func loadData() async {
guard let result = try? await DataService.shared.fetchItems() else { return }
data = result
}
}
// ② 手动创建 Task
class DataManager {
private var loadTask: Task<Void, Never>?
func startLoading() {
// 取消之前的任务,避免重复请求
loadTask?.cancel()
loadTask = Task {
do {
let items = try await DataService.shared.fetchItems()
// 检查是否被取消
guard !Task.isCancelled else { return }
await updateUI(with: items)
} catch is CancellationError {
print("任务被取消")
} catch {
print("加载失败:\(error)")
}
}
}
func stopLoading() {
loadTask?.cancel()
loadTask = nil
}
}
// ③ Task 优先级
Task(priority: .high) { /* 高优先级 */ }
Task(priority: .medium) { /* 默认 */ }
Task(priority: .low) { /* 低优先级 */ }
Task(priority: .background) { /* 后台,不占用主资源 */ }
Task(priority: .utility) { /* 工具类任务 */ }
// ④ Task.detached(脱离结构化并发,不继承上下文)
Task.detached(priority: .background) {
await performHeavyComputation()
}
// ⑤ 手动检测取消
func processItems(_ items: [Item]) async throws {
for item in items {
try Task.checkCancellation() // 如果已取消,抛出 CancellationError
await process(item)
}
}
8.3 async let - 并行执行
async let 是最简洁的并行执行方式,适合固定数量的并行任务。
swift
// 串行执行(❌ 低效:总耗时 = 各任务耗时之和)
func loadDashboardSerial() async throws -> Dashboard {
let profile = try await fetchProfile() // 等待完成
let articles = try await fetchArticles() // 再等待
let notifications = try await fetchNotifs() // 再等待
// 总耗时:3秒(假设每个 1 秒)
return Dashboard(profile: profile, articles: articles, notifications: notifications)
}
// 并行执行(✅ 高效:总耗时 = 最慢的任务)
func loadDashboardParallel() async throws -> Dashboard {
async let profile = fetchProfile() // 立即启动,不等待
async let articles = fetchArticles() // 立即启动
async let notifications = fetchNotifs() // 立即启动
// 在这里等待所有任务完成
let (p, a, n) = try await (profile, articles, notifications)
// 总耗时:~1 秒(三个任务并行)
return Dashboard(profile: p, articles: a, notifications: n)
}
8.4 TaskGroup - 动态并行任务
适合数量不固定的并行任务,比如批量下载图片、批量处理数据。
swift
// 并发下载图片(数量动态)
func downloadImages(urls: [URL]) async -> [UIImage] {
await withTaskGroup(of: (Int, UIImage?).self) { group in
// 添加所有任务(并行开始)
for (index, url) in urls.enumerated() {
group.addTask {
let image = try? await URLSession.shared.image(from: url)
return (index, image)
}
}
// 收集结果(按完成顺序收集)
var results = [(Int, UIImage?)]()
for await result in group {
results.append(result)
}
// 按原始顺序排列
return results.sorted { $0.0 < $1.0 }.compactMap { $0.1 }
}
}
// 带错误处理的 TaskGroup
func processBatch(_ items: [DataItem]) async throws -> [ProcessedItem] {
try await withThrowingTaskGroup(of: ProcessedItem.self) { group in
for item in items {
group.addTask {
try await processItem(item)
}
}
var results = [ProcessedItem]()
for try await result in group {
results.append(result)
}
return results
}
}
// 限制并发数(避免资源过载)
func downloadWithLimit(urls: [URL], maxConcurrent: Int = 5) async -> [Data?] {
var results = [Data?](repeating: nil, count: urls.count)
await withTaskGroup(of: (Int, Data?).self) { group in
var pending = urls.enumerated().makeIterator()
// 初始添加 maxConcurrent 个任务
for _ in 0..<min(maxConcurrent, urls.count) {
if let (index, url) = pending.next() {
group.addTask {
let data = try? await URLSession.shared.data(from: url).0
return (index, data)
}
}
}
// 一个完成,添加下一个(保持并发数不超限)
for await (index, data) in group {
results[index] = data
if let (nextIndex, nextURL) = pending.next() {
group.addTask {
let data = try? await URLSession.shared.data(from: nextURL).0
return (nextIndex, data)
}
}
}
}
return results
}
8.5 Actor - 并发安全
Actor 保证内部状态在任何时刻只被一个任务访问,消灭数据竞争。
swift
// 传统方式(DispatchQueue 手动加锁,容易出错)
class UnsafeCounter {
private var value = 0
private let queue = DispatchQueue(label: "counter.queue")
func increment() {
queue.sync { value += 1 } // 容易忘记加锁
}
}
// Actor 方式(编译器自动保证线程安全)
actor SafeCounter {
private(set) var value = 0
func increment() {
value += 1 // 无需手动加锁,Actor 自动序列化访问
}
func reset() {
value = 0
}
// nonisolated:不需要隔离的操作(可同步调用)
nonisolated var description: String {
"SafeCounter"
}
}
// 使用 Actor(必须用 await)
let counter = SafeCounter()
await counter.increment()
await counter.increment()
let value = await counter.value // 读取也需要 await
print("计数:\(value)") // 2
// 实际场景:图片缓存 Actor
actor ImageCache {
private var cache: [URL: UIImage] = [:]
func image(for url: URL) -> UIImage? {
cache[url]
}
func store(_ image: UIImage, for url: URL) {
cache[url] = image
}
func removeAll() {
cache.removeAll()
}
var count: Int { cache.count }
}
// 多个 Task 并发访问,Actor 自动排队,无数据竞争
let cache = ImageCache()
await withTaskGroup(of: Void.self) { group in
for url in imageURLs {
group.addTask {
if await cache.image(for: url) == nil {
let image = try? await URLSession.shared.image(from: url)
if let image {
await cache.store(image, for: url)
}
}
}
}
}
8.6 @MainActor - 主线程约束
swift
// @MainActor 标记的代码总在主线程执行,安全更新 UI
@MainActor
class HomeViewModel: ObservableObject {
var articles: [Article] = []
var isLoading = false
func loadArticles() async {
isLoading = true
// Task.detached 在后台线程执行
let articles = await Task.detached(priority: .background) {
try? await ArticleService.shared.fetchAll()
}.value
// 回到主线程(@MainActor 保证)
self.articles = articles ?? []
self.isLoading = false
}
}
// 在非 @MainActor 函数中切换到主线程
func handleNetworkResponse() async {
let data = try? await NetworkClient.shared.fetchData()
// 切换到主线程更新 UI
await MainActor.run {
updateUI(with: data)
}
}
// Sendable - 标记可以安全跨越并发域的类型
struct SafeMessage: Sendable {
let id: UUID
let text: String // 不可变值类型,天然安全
}
// 非 Sendable 类型(class 默认不 Sendable)跨域传递会有编译警告
8.7 AsyncStream - 异步序列
swift
// 将 Delegate/回调 桥接为 AsyncStream
func locationUpdates() -> AsyncStream<CLLocation> {
AsyncStream { continuation in
let manager = CLLocationManager()
let delegate = LocationDelegate { location in
continuation.yield(location) // 发送值
}
manager.delegate = delegate
manager.startUpdatingLocation()
continuation.onTermination = { _ in
manager.stopUpdatingLocation()
}
}
}
// 消费 AsyncStream(for await)
Task {
for await location in locationUpdates() {
print("位置更新:\(location.coordinate)")
if reachedDestination(location) {
break // 停止监听
}
}
}
// AsyncThrowingStream(可抛出错误)
func webSocketMessages(url: URL) -> AsyncThrowingStream<String, Error> {
AsyncThrowingStream { continuation in
Task {
let ws = URLSession.shared.webSocketTask(with: url)
ws.resume()
while !Task.isCancelled {
do {
let message = try await ws.receive()
if case .string(let text) = message {
continuation.yield(text)
}
} catch {
continuation.finish(throwing: error)
break
}
}
}
}
}
章节总结
| 并发工具 | 特点 | 适用场景 |
|---|---|---|
async/await |
清晰的异步代码 | 所有异步操作 |
.task 修饰符 |
与视图生命周期绑定 | SwiftUI 数据加载 |
async let |
固定数量并行 | 3-5个并行请求 |
withTaskGroup |
动态数量并行 | 批量下载/处理 |
Actor |
数据竞争保护 | 共享可变状态 |
@MainActor |
主线程保证 | UI 更新必用 |
AsyncStream |
将回调转为异步序列 | 位置、传感器等流式数据 |
Demo 说明
| 文件 | 演示内容 |
|---|---|
AsyncAwaitBasicsDemo.swift |
async/await / Task 取消 |
ParallelExecutionDemo.swift |
async let / TaskGroup 并行 |
ActorDemo.swift |
Actor 线程安全计数器 + 图片缓存 |
MainActorDemo.swift |
@MainActor UI 更新 |
AsyncStreamDemo.swift |
位置更新 AsyncStream |
📎 扩展内容补充
来源:第八章_工程化.md
本章概述:学习 iOS 工程化实践,包含 Swift Package Manager 依赖管理、多环境配置(Debug/Staging/Release)、OSLog 日志系统、启动优化等。
8.1 Swift Package Manager 依赖管理
概念讲解
SPM 是 Apple 官方包管理器,类比 Flutter 的 pubspec.yaml + pub.dev。
添加依赖
Xcode → File → Add Package Dependencies
输入 GitHub URL,选择版本规则:
- Exact Version: 固定版本(最稳定)
- Up to Next Major: 允许小版本更新
- Branch: 跟踪分支(不推荐生产)
常用依赖库
swift
// Package.swift(库开发/命令行工具)
let package = Package(
name: "MyiOSApp",
platforms: [.iOS(.v17)],
dependencies: [
// TCA - 架构框架
.package(url: "https://github.com/pointfreeco/swift-composable-architecture",
from: "1.0.0"),
// Kingfisher - 图片缓存
.package(url: "https://github.com/onevcat/Kingfisher",
from: "7.0.0"),
// Alamofire - 网络请求
.package(url: "https://github.com/Alamofire/Alamofire",
from: "5.0.0"),
// Lottie - 动画
.package(url: "https://github.com/airbnb/lottie-ios",
from: "4.0.0"),
]
)
常用第三方库推荐
| 类别 | 库名 | 说明 | 对应Flutter |
|---|---|---|---|
| 架构 | TCA | 单向数据流架构 | BLoC |
| 网络 | Alamofire | HTTP 网络请求 | Dio |
| 图片 | Kingfisher | 图片缓存 | CachedNetworkImage |
| 动画 | Lottie | JSON 动画播放 | lottie |
| 数据库 | GRDB | SQLite ORM | sqflite |
| 日志 | CocoaLumberjack | 高性能日志 | logger |
| 测试 | Quick/Nimble | BDD 测试框架 | - |
8.2 多环境配置(Debug/Staging/Release)
概念讲解
类比 Flutter 的 --dart-define + Flavors 方案。
使用 xcconfig 配置
bash
# Debug.xcconfig
BASE_URL = https://dev-api.example.com
BUNDLE_ID_SUFFIX = .debug
APP_NAME = MyApp(Dev)
# Staging.xcconfig
BASE_URL = https://staging-api.example.com
BUNDLE_ID_SUFFIX = .staging
APP_NAME = MyApp(Staging)
# Release.xcconfig
BASE_URL = https://api.example.com
BUNDLE_ID_SUFFIX =
APP_NAME = MyApp
在 Swift 中读取配置
swift
// 从 Info.plist 读取环境配置
enum AppEnvironment {
case debug
case staging
case release
static var current: AppEnvironment {
#if DEBUG
return .debug
#elseif STAGING
return .staging
#else
return .release
#endif
}
var baseURL: String {
// 从 Info.plist 中读取(通过 xcconfig 注入)
Bundle.main.infoDictionary?["BASE_URL"] as? String
?? "https://api.example.com"
}
var isDebug: Bool { self == .debug }
var analyticsEnabled: Bool {
switch self {
case .debug: return false
case .staging: return true // Staging 收集数据用于测试
case .release: return true
}
}
}
// 全局访问
let apiBaseURL = AppEnvironment.current.baseURL
编译条件(#if)
swift
struct DebugView: View {
var body: some View {
VStack {
#if DEBUG
// 仅在 Debug 环境显示调试信息(不会编译进 Release 包)
DebugConsoleView()
#endif
MainContentView()
}
}
}
8.3 日志系统与调试
概念讲解
iOS 17 推荐使用 OSLog / Logger,性能远优于 print()。
swift
import OSLog
// 定义日志分类
extension Logger {
static let network = Logger(subsystem: Bundle.main.bundleIdentifier!,
category: "Network")
static let ui = Logger(subsystem: Bundle.main.bundleIdentifier!,
category: "UI")
static let data = Logger(subsystem: Bundle.main.bundleIdentifier!,
category: "Data")
static let auth = Logger(subsystem: Bundle.main.bundleIdentifier!,
category: "Auth")
}
// 使用(类比 Flutter 的 logger 库)
class NetworkService {
func fetchData(from url: URL) async throws -> Data {
Logger.network.info("🌐 开始请求: \(url.absoluteString)")
do {
let (data, response) = try await URLSession.shared.data(from: url)
Logger.network.info("✅ 请求成功: \(data.count) bytes")
return data
} catch {
Logger.network.error("❌ 请求失败: \(error.localizedDescription)")
throw error
}
}
}
// 日志级别
Logger.network.debug("调试信息") // 仅Debug
Logger.network.info("普通信息") // Debug + Release
Logger.network.warning("警告信息") // 重要
Logger.network.error("错误信息") // 错误
Logger.network.critical("严重错误") // 崩溃级别
// 使用 Console.app 查看日志
// 连接真机后可实时过滤查看
全局错误捕获
swift
// 在 App 入口设置全局崩溃处理
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 捕获未处理的 Swift 错误(需要第三方库如 Sentry)
SentrySDK.start { options in
options.dsn = "https://your-sentry-dsn"
options.debug = AppEnvironment.current.isDebug
options.tracesSampleRate = 1.0
options.enableMetricKit = true
}
return true
}
}
8.4 启动优化
概念讲解
swift
// 1. 使用系统 LaunchScreen(Info.plist 配置)
// UILaunchScreen → UIColorName → AppBackground
// 2. 延迟初始化(类比 Flutter 的 Flutter.ensureInitialized 之后再做耗时操作)
@main
struct iOSDemosApp: App {
init() {
// ✅ 只做必要的轻量初始化
AppSettings.configure()
// ❌ 不要在这里做:
// - 网络请求
// - 大文件读取
// - 复杂计算
}
var body: some Scene {
WindowGroup {
ContentView()
.task {
// App 展示后再做重型初始化
await AppInitializer.shared.initialize()
}
}
}
}
// 3. 并行初始化
actor AppInitializer {
static let shared = AppInitializer()
private var isInitialized = false
func initialize() async {
guard !isInitialized else { return }
// 并行执行多个初始化任务
async let authTask: Void = AuthManager.shared.restoreSession()
async let configTask: Void = RemoteConfig.shared.fetch()
async let cacheTask: Void = CacheManager.shared.warmUp()
await (authTask, configTask, cacheTask)
isInitialized = true
}
}
章节总结
| 工程化实践 | 工具/技术 | 对应Flutter |
|---|---|---|
| 依赖管理 | SPM / CocoaPods | pubspec.yaml |
| 多环境配置 | xcconfig + Info.plist | --dart-define / Flavors |
| 日志系统 | OSLog / Logger | logger |
| 错误监控 | Sentry / Crashlytics | Sentry / Firebase Crashlytics |
| 启动优化 | 延迟+并行初始化 | 延迟初始化 |
Demo 说明
| Demo 文件 | 演示内容 |
|---|---|
DependencyManagementDemo.swift |
SPM 常用库展示 |
MultiEnvironmentDemo.swift |
多环境配置读取 |
LoggingDemo.swift |
OSLog 日志分级演示 |
LaunchOptimizationDemo.swift |
启动流程 + 并行初始化 |