第八章:iOS并发编程

本章深入讲解 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 启动流程 + 并行初始化
相关推荐
算是难了2 小时前
macOS常用终端命令
macos
空中海4 小时前
第五章:i OS状态与数据流管理
ios
花间相见6 小时前
【大模型微调与部署01】—— ms-swift-3.12入门:安装、快速上手
开发语言·ios·swift
空中海6 小时前
第一章:Swift 语言核心
ios·cocoa·swift
小码过河.8 小时前
本地端侧GUI智能体自动化操作电脑Mano-P:macOS版本安装与使用全指南
macos·ai·自动化
90后的晨仔8 小时前
《SwiftUI 进阶第6章:列表与滚动视图》
ios
空中海8 小时前
第十章:iOS架构设计与工程化
macos·ios·cocoa
90后的晨仔15 小时前
《SwiftUI 进阶第7章:导航系统》
ios
90后的晨仔15 小时前
《swiftUI进阶 第9章SwiftUI 状态管理完全指南》
ios