Swift 并发 Combine响应式框架

Swift 并发与 Combine 详解

Swift 提供了两种强大的异步编程范式:Swift 并发模型 (async/await、Task、Actor)和Combine 响应式框架。它们各有优势,可单独使用也可协同工作,解决不同类型的异步编程问题。


一、Swift 并发模型(Swift 5.5+)

核心概念

Swift 并发模型基于结构化并发actor 模型,提供了更直观、更安全的异步编程方式,替代了传统的回调地狱。

组件 作用 关键特性
async/await 异步函数标记与等待 让异步代码看起来像同步代码,消除嵌套回调
Task 异步任务单元 可取消、有优先级、支持任务组
TaskGroup 管理一组相关任务 等待所有任务完成、聚合结果、支持取消传播
Actor 数据隔离与同步 确保状态安全访问,自动处理线程同步
Sendable 值类型 / 函数安全传递 确保跨线程传递的数据是线程安全的
MainActor 主线程执行保证 标记 UI 相关代码在主线程执行

基础用法示例

Swift 复制代码
// 异步函数定义
func fetchUserData(userId: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(userId)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// 调用异步函数
Task {
    do {
        let user = try await fetchUserData(userId: 123)
        print("用户数据: \(user)")
    } catch {
        print("获取用户数据失败: \(error)")
    }
}

// 任务组并行执行
func fetchMultipleUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUserData(userId: id)
            }
        }
        
        var users = [User]()
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

核心优势

  1. 可读性强:异步代码线性编写,无回调嵌套
  2. 错误处理自然 :使用 try/catch 处理异步错误,与同步代码一致
  3. 结构化并发:任务层级清晰,自动管理生命周期,防止资源泄漏
  4. 线程安全 :通过 ActorSendable 确保数据访问安全

第一部分:Combine 超详细精讲

核心定位

Combine 是苹果官方的响应式编程框架 ,专门处理随时间变化的异步事件 (网络请求、按钮点击、输入框输入、定时器、KVO)。一句话总结:生产者发数据 → 管道加工数据 → 消费者用数据,全程链式调用,无回调嵌套。


1. Combine 四大核心组件(死记硬背)

组件名 作用 必知细节
Publisher (发布者) 生产 / 发送数据 1. 不订阅就不发数据;2. 只能发 3 种事件:值、完成、错误;3. 泛型:<输出值类型,错误类型>
Subscriber (订阅者) 接收 / 处理数据 最常用 2 种:sink(接收值 + 完成)、assign(绑定到属性)
Operator (操作符) 加工数据 链式调用,转换 / 过滤 / 合并数据(map/filter/debounce)
Cancellable (订阅持有者) 保留订阅 不持有就直接销毁订阅 ,必须存到 Set<AnyCancellable>

2. 最基础的完整流程(逐行解析)

Swift 复制代码
import Combine

// 1. 创建发布者:Just = 发一个值就结束,错误类型Never(永不报错)
let publisher = Just("Hello Combine")

// 2. 订阅持有者:必须创建,否则订阅瞬间销毁
var cancellables = Set<AnyCancellable>()

// 3. 订阅 + 处理数据
publisher
    .sink(
        receiveCompletion: { completion in
            // 接收完成事件:只有2种情况 .finished(正常结束)/.failure(报错)
            print("订阅结束: \(completion)")
        },
        receiveValue: { value in
            // 接收数据
            print("收到值: \(value)")
        }
    )
    .store(in: &cancellables) // 4. 保存订阅(核心!不写这行=白写)

✅ 输出:收到值: Hello Combine订阅结束: finished


3. 核心发布者:Subject(手动发数据,最常用)

Subject 是可以手动调用 send () 发值 的发布者,分两种,细节区别必考

① PassthroughSubject(不存历史值)

  • 只发给当前已订阅的订阅者
  • 订阅前发的值,收不到
Swift 复制代码
let subject = PassthroughSubject<String, Never>()
subject.send("第一条") // 订阅前发的,收不到

// 订阅
subject.sink{ print($0) }.store(in: &cancellables)

subject.send("第二条") // 收到
subject.send(completion: .finished) // 结束

✅ 输出:第二条

② CurrentValueSubject(保存最新值)

  • 订阅瞬间会收到最后一次发送的值
  • 必须初始化一个默认值
Swift 复制代码
let subject = CurrentValueSubject<String, Never>("默认值")
subject.sink{ print($0) }.store(in: &cancellables) // 订阅直接收默认值
subject.send("新值")

✅ 输出:默认值 → 新值


4. 必学操作符(工作中 90% 使用率)

① map:转换数据类型

Swift 复制代码
Just(5)
    .map { $0 * 2 } // 5 → 10
    .sink{ print($0) }
    .store(in: &cancellables)

② filter:过滤数据

Swift 复制代码
Just(18)
    .filter { $0 >= 18 } // 满足条件才往下发
    .sink{ print("成年: \($0)") }
    .store(in: &cancellables)

③ debounce:防抖(输入框神器)

  • 停止输入 N 秒后才发值,避免频繁请求
Swift 复制代码
let subject = PassthroughSubject<String, Never>()
subject
    .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
    .sink{ print("输入内容: \($0)") }
    .store(in: &cancellables)

subject.send("h")
subject.send("he")
subject.send("hello") // 0.5秒后才收到

④ receive (on:):切换线程(UI 更新必备)

  • 网络请求在子线程,更新 UI 必须切回主线程
Swift 复制代码
URLSession.shared.dataTaskPublisher(for: url)
    .receive(on: DispatchQueue.main) // 切主线程更新UI
    .sink{ _ in } receiveValue: { _ in }

⑤ flatMap:串联异步请求(先请求 A,再用 A 的结果请求 B)

Swift 复制代码
// 先获取用户ID → 再用ID请求用户详情
fetchUserIdPublisher()
    .flatMap { userId in fetchUserDetailPublisher(id: userId) }
    .sink{ _ in }

5. Combine 网络请求(标准写法)

Swift 复制代码
// 定义模型
struct User: Codable { let name: String }

let url = URL(string: "https://api.github.com/users/apple")!

URLSession.shared.dataTaskPublisher(for: url)
    .map(\.data) // 只取数据,忽略响应
    .decode(type: User.self, decoder: JSONDecoder()) // 解码模型
    .receive(on: DispatchQueue.main) // 切主线程
    .sink(
        receiveCompletion: { completion in
            if case .failure(let error) = completion {
                print("请求失败: \(error)")
            }
        },
        receiveValue: { user in
            print("用户名: \(user.name)")
        }
    )
    .store(in: &cancellables)

6. Combine 必踩坑(细节!)

  1. 不存 cancellable → 订阅直接失效
  2. 错误事件会终止订阅(发一次错误,整个流结束)
  3. Subject 不 send (completion) → 内存泄漏
  4. UI 操作必须用 receive (on: .main)

第二部分:Swift 并发 超详细精讲(Swift5.5+,iOS15+)

核心定位

Swift 并发是原生结构化并发 ,专门处理一次性异步操作 (网络请求、耗时计算、并行任务),用 async/await 把异步代码写成同步风格,彻底消灭回调地狱。


1. 核心关键字(死记硬背)

关键字 作用 细节
async 标记异步函数 函数内部可以执行耗时操作,不阻塞线程
await 等待异步函数完成 只能在 async 上下文调用
Task 创建异步任务 把异步函数放到任务里执行
TaskGroup 并行执行多个任务 自动管理生命周期,支持取消
Actor 线程安全数据隔离 解决多线程数据竞争,自动加锁
MainActor 主线程执行 UI 更新必须用

2. 最基础流程:async/await(逐行解析)

步骤 1:定义异步函数

Swift 复制代码
// async 标记:这是一个异步函数,会耗时
func fetchUser() async throws -> User {
    let url = URL(string: "https://api.github.com/users/apple")!
    // await 等待:不阻塞线程,等网络请求完成
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

步骤 2:执行异步函数(必须包在 Task 里)

Swift 复制代码
// Task:创建异步上下文,才能调用 await
Task {
    do {
        let user = try await fetchUser()
        print("用户: \(user.name)")
    } catch {
        print("错误: \(error)")
    }
}

✅ 这就是线性异步代码,没有任何嵌套!


3. Task 细节(必知)

① 取消任务

Swift 复制代码
let task = Task {
    try await fetchUser()
}
// 取消请求
task.cancel()

② 主线程 Task(UI 更新)

Swift 复制代码
// @MainActor = 切主线程
Task { @MainActor in
    label.text = "加载完成"
}

4. 并行任务:TaskGroup(核心高级用法)

场景:同时请求 3 个用户数据,等全部完成再汇总

Swift 复制代码
func fetchAllUsers() async throws -> [User] {
    // 任务组:管理多个并行异步任务
    try await withThrowingTaskGroup(of: User.self) { group in
        // 添加3个并行任务
        group.addTask { try await fetchUser(id: 1) }
        group.addTask { try await fetchUser(id: 2) }
        group.addTask { try await fetchUser(id: 3) }
        
        // 收集所有结果
        var users = [User]()
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

✅ 自动并行、自动等待、自动取消、无内存泄漏


5. Actor:线程安全(解决多线程崩溃)

问题:多线程同时修改同一个变量 → 崩溃Actor 解决方案:自动隔离数据,同一时间只允许一个线程访问

Swift 复制代码
// 定义Actor:线程安全容器
actor UserStore {
    var users: [User] = []
    
    func addUser(_ user: User) {
        users.append(user)
    }
}

// 使用:必须 await 调用
Task {
    let store = UserStore()
    await store.addUser(User(name: "A"))
}

✅ 无需手动加锁,绝对线程安全


6. Swift 并发 必踩坑

  1. async 函数不能直接调用 → 必须包 Task
  2. await 只能在 async 上下文
  3. UI 必须用 @MainActor
  4. 错误必须用 do/catch 处理

第三部分:Combine ↔ Swift 并发 互操作(细节)

1. Combine → 并发(用 values)

所有 Publisher 都有 .values 属性,直接转异步序列

Swift 复制代码
Task {
    // Combine 发布者 → 异步序列
    let publisher = Just("test")
    for await value in publisher.values {
        print(value)
    }
}

2. 并发 → Combine(用 Future)

把 async 函数包成 Combine 发布者

Swift 复制代码
func asyncToCombine() -> AnyPublisher<User, Error> {
    Future { promise in
        Task {
            do {
                let user = try await fetchUser()
                promise(.success(user))
            } catch {
                promise(.failure(error))
            }
        }
    }
    .eraseToAnyPublisher()
}

第四部分:终极选型指南(什么时候用哪个?)

场景 选 Combine 选 Swift 并发
输入框防抖、按钮点击、实时搜索
复杂数据流转换、串联请求
单次网络请求、耗时计算
并行执行多个任务
线程安全数据管理
SwiftUI 状态绑定

总结(最细核心)

  1. Combine = 处理持续数据流,靠发布者 + 订阅者 + 操作符,必须持有 cancellable
  2. Swift 并发 = 处理一次性异步任务,靠 async/await/Task,结构化并发无泄漏
  3. 互操作 :Combine 转并发用 .values,并发转 Combine 用 Future
  4. 开发标配:UI 事件用 Combine,网络 / 并行用 Swift 并发
相关推荐
万法若空2 小时前
C++ <memory> 库全方位详解
开发语言·c++
代码中介商2 小时前
C++ 类型转换深度解析:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
青小莫2 小时前
C++之string(OJ练习)
开发语言·c++·stl
freshman_y2 小时前
一篇介绍C语言中二级指针和二维数组的文章
c语言·开发语言
-Marks-2 小时前
【C++编程】STL简介 --- (是什么 | 版本发展历程 | 六大组件 | 重要性缺陷以及如何学习)
开发语言·c++·学习·stl·stl版本
HealthScience3 小时前
【Bib 2026】基因最新综述(有什么任务、benchmark、代表性模型)
android·开发语言·kotlin
wjs20243 小时前
CSS 网格元素
开发语言
Java小白笔记3 小时前
OpenClaw 实战方法论
java·开发语言·人工智能·ai·全文检索·ai编程·ai写作