Swift Combine 入门:基本概念、网络请求与 SwiftUI 用法

Elena
Elena
发布时间:2026-08-24 | 37人浏览过
原创

前言

在 iOS 13 及更高版本里,Apple 提供了 Combine 作为处理异步事件和数据流的响应式框架。很多开发者第一次接触它时,往往会被发布者、订阅者、操作符这些概念同时出现弄乱。本文按“先理解模型,再看代码,再落到网络请求和 SwiftUI”的顺序整理一遍,帮助你判断 Combine 到底解决什么问题、常见链路怎么写,以及它和界面状态更新是如何接上的。

在 iOS 13 及更高版本里,Apple 提供了 Combine 作为处理异步事件和数据流的响应式框架。很多开发者第一次接触它时,往往会被发布者、订阅者、操作符这些概念同时出现弄乱。本文按“先理解模型,再看代码,再落到网络请求和 SwiftUI”的顺序整理一遍,帮助你判断 Combine 到底解决什么问题、常见链路怎么写,以及它和界面状态更新是如何接上的。

Combine 核心解决什么问题

Combine 的目标,是用声明式方式处理异步事件和连续数据流。相比把回调、状态更新和错误处理分散在多个位置,Combine 倾向于把这些步骤串成一条清晰的数据处理链。

它适合的场景包括:

  • 一次或多次异步数据返回,例如网络请求、表单输入、通知事件。
  • 需要在接收数据后继续做转换、过滤、组合等处理。
  • 希望把数据变化直接联动到界面状态,尤其是在 SwiftUI 中。

先看三个基础概念:Publisher、Subscriber 和 Operators

Publisher:数据从哪里来

Publisher 是数据的来源。它可以发送值、发送错误,并在最后进入完成状态。

展示 Combine 的三大核心角色以及数据如何沿发布、转换、订阅的方向流动。
Combine 三大核心角色关系图用一张关系图先看懂 Combine 的基础模型,再读代码更容易对上各环节。

Combine 内置了几类很常见的发布者:

  • Just:发送一个值后立即完成。
  • PassthroughSubject:适合在运行过程中主动发送多个值,也可以手动结束。
  • CurrentValueSubject:除了能持续发送新值,还会持有一个当前值。

Subscriber:谁来接收结果

Subscriber 是接收数据的一方,负责处理发布者送出的值、错误和完成事件。实际开发中,最常见的接收方式之一就是 sink。

Operators:中间这条链怎么加工数据

操作符负责对数据流做处理。你可以把它理解成发布者和订阅者之间的加工管道。

  • map:把收到的值转换成另一种形式。
  • filter:按条件保留部分值。
  • combineLatest:把多个发布者的最新值组合成新的结果。

这三个角色拼起来,基本就是 Combine 的最小工作模型:发布数据、沿链路处理、在末端消费。

从一个最小示例理解 Combine 的执行链

如果你只想先看 Combine 最基础的工作方式,可以从 Just 开始。这个例子展示了发布者如何发出值,操作符如何改写内容,以及订阅者如何分别处理完成和结果。

import Combine
import Foundation

// 创建一个发布者
let publisher = Just("Hello, Combine!")

// 订阅发布者
let cancellable = publisher
    .map { "($0) World!" }  // 使用 map 操作符转换数据
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Completed")
        case .failure(let error):
            print("Error: (error)")
        }
    }, receiveValue: { value in
        print(value)  // 接收转换后的值
    })

这段代码可以直接拆成三步理解:

  • Just("Hello, Combine!") 创建一个只发送一次内容的发布者。
  • map 把原始字符串转换为新的字符串。
  • sink 负责接收最终结果,并监听流程何时结束。

它很适合拿来理解 Combine 的语法结构,因为链条短、数据路径清楚,也容易看出“声明式拼接”与传统回调写法的差异。

用 Combine 处理异步网络请求

真正能体现 Combine 价值的,往往是异步任务。比如网络请求:请求发起、响应提取、JSON 解码、主线程切换和结果消费,本来会分散在多个步骤里,但在 Combine 中可以写成一条连续的数据流。

展示 URLSession 网络请求在 Combine 中从请求发起到主线程回调的完整处理步骤。
Combine 网络请求处理链网络请求是 Combine 最常见的落地场景之一,适合用流程图把每个操作符的职责拆开。
import Combine
import Foundation

// 定义一个模型
struct Post: Codable {
    let id: Int
    let title: String
}

// 创建一个发布者,用于网络请求
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
    .map { $0.data } // 只提取数据
    .decode(type: [Post].self, decoder: JSONDecoder()) // 解码成模型
    .receive(on: RunLoop.main) // 切换到主线程
    .eraseToAnyPublisher() // 将发布者转换为 AnyPublisher

// 订阅发布者
let cancellable = publisher
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Completed")
        case .failure(let error):
            print("Error: (error)")
        }
    }, receiveValue: { posts in
        print(posts) // 打印获取到的帖子
    })

这条请求链分别做了什么

  • dataTaskPublisher(for: url):把一次网络请求包装成发布者。
  • .map { $0.data }:从返回值中只取出响应数据。
  • .decode(type: [Post].self, decoder: JSONDecoder()):把 JSON 解码为 [Post]。
  • .receive(on: RunLoop.main):把后续结果切换到主线程,方便更新 UI。
  • .eraseToAnyPublisher():抹平具体发布者类型,便于统一暴露接口。

如果你之前用过回调或闭包写网络层,会发现 Combine 的优势在于:数据处理顺序和线程切换逻辑都能沿着一条链直接读下来,代码意图更集中。

Combine 如何和 SwiftUI 状态管理配合

Combine 与 SwiftUI 的结合点,核心就在状态变化自动驱动界面更新。常见做法是:在模型层使用 @Published 发布状态,在视图层通过 @StateObject 或 @ObservedObject 观察它。

展示 Combine 与 SwiftUI 配合时,ViewModel、@Published 和视图刷新之间的状态流转关系。
Combine 与 SwiftUI 的状态更这张图聚焦状态绑定,说明网络结果如何进入 ViewModel,再驱动 SwiftUI 列表刷新。
import SwiftUI
import Combine

class ViewModel: ObservableObject {
    @Published var posts: [Post] = []
    
    private var cancellables = Set<AnyCancellable>()
    
    func fetchPosts() {
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
        
        URLSession.shared.dataTaskPublisher(for: url)
            .map { $0.data }
            .decode(type: [Post].self, decoder: JSONDecoder())
            .receive(on: RunLoop.main)
            .replaceError(with: [])
            .assign(to: &$posts) // 将结果分配给 @Published 属性
    }
}

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        List(viewModel.posts) { post in
            Text(post.title)
        }
        .onAppear {
            viewModel.fetchPosts() // 视图出现时调用数据获取
        }
    }
}

这段 SwiftUI 示例的关键点

  • ViewModel 遵循 ObservableObject,说明它是可被界面观察的状态对象。
  • @Published var posts 用来把数组变化发布出去。
  • assign(to: &$posts) 直接把请求结果写回已发布属性。
  • @StateObject private var viewModel = ViewModel() 让视图持有并观察这个状态源。
  • List(viewModel.posts) 会随着 posts 更新自动刷新内容。

这也是 Combine 在 SwiftUI 中最常见的落点之一:让异步数据流不必手动一层层回传,而是自然汇入视图状态。

什么时候适合用 Combine

如果你的代码里存在明显的数据流和异步链路,比如网络请求、输入监听、状态同步、多个事件源组合,那么 Combine 往往能带来更清晰的结构。它的核心价值不是“把代码写得更短”,而是把值、错误、完成和线程切换放进同一条可追踪的处理路径里。

从本文几个例子看,可以这样快速判断:

  • 只想理解语法结构,先从 Just、map、sink 入手。
  • 要处理真实异步任务,可以重点掌握 dataTaskPublisher、decode、receive(on:)。
  • 如果项目使用 SwiftUI,那么 @Published、ObservableObject 和 Combine 的组合非常值得熟悉。

总体来看,Combine 为 iOS 13 及以上版本提供了一套完整的数据流表达方式。理解发布者、订阅者和操作符之后,再回头看网络层和 SwiftUI 状态更新,整套思路会更容易串起来。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表