Combine 基本使用指南

Combine 基本使用指南

Combine 是 Apple 在 2019 年推出的响应式编程框架,用于处理随时间变化的值流。下面是 Combine 的基本概念和使用方法。

核心概念

1. Publisher(发布者)

  • 产生值的源头
  • 可以发送 0 个或多个值
  • 可能以完成或错误结束

2. Subscriber(订阅者)

  • 接收来自 Publisher 的值
  • 可以控制数据流的需求

3. Operator(操作符)

  • 转换、过滤、组合来自 Publisher 的值

基本使用示例

创建简单的 Publisher

swift 复制代码
import Combine

// 1. Just - 发送单个值然后完成
let justPublisher = Just("Hello, World!")

// 2. Sequence - 发送序列中的值
let sequencePublisher = [1, 2, 3, 4, 5].publisher

// 3. Future - 异步操作的结果
func fetchData() -> Future<String, Error> {
    return Future { promise in
        // 模拟异步操作
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            promise(.success("Data fetched"))
        }
    }
}

// 4. @Published 属性包装器
class DataModel {
    @Published var name: String = "Initial"
}

订阅 Publisher

swift 复制代码
// 使用 sink 订阅
var cancellables = Set<AnyCancellable>()

// 订阅 Just
justPublisher
    .sink { value in
        print("Received value: \(value)")
    }
    .store(in: &cancellables)

// 订阅 Sequence
sequencePublisher
    .sink(
        receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("Finished successfully")
            case .failure(let error):
                print("Failed with error: \(error)")
            }
        },
        receiveValue: { value in
            print("Received: \(value)")
        }
    )
    .store(in: &cancellables)

常用操作符

swift 复制代码
// 转换操作符
sequencePublisher
    .map { $0 * 2 }                    // 转换每个值
    .filter { $0 > 5 }                 // 过滤值
    .reduce(0, +)                      // 聚合值
    .sink { print("Result: \($0)") }
    .store(in: &cancellables)

// 组合操作符
let publisher1 = [1, 2, 3].publisher
let publisher2 = ["A", "B", "C"].publisher

Publishers.Zip(publisher1, publisher2)
    .sink { print("Zipped: \($0), \($1)") }
    .store(in: &cancellables)

// 错误处理
enum MyError: Error {
    case testError
}

Fail(outputType: String.self, failure: MyError.testError)
    .catch { error in
        Just("Recovered from error")
    }
    .sink { print($0) }
    .store(in: &cancellables)

处理 UI 更新

swift 复制代码
import UIKit
import Combine

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var button: UIButton!
    
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupBindings()
    }
    
    private func setupBindings() {
        // 监听文本框变化
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: textField)
            .compactMap { ($0.object as? UITextField)?.text }
            .sink { [weak self] text in
                self?.label.text = "You typed: \(text)"
            }
            .store(in: &cancellables)
        
        // 按钮点击事件
        button.publisher(for: .touchUpInside)
            .sink { [weak self] _ in
                self?.handleButtonTap()
            }
            .store(in: &cancellables)
    }
    
    private func handleButtonTap() {
        print("Button tapped!")
    }
}

网络请求示例

swift 复制代码
struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

class UserService {
    private var cancellables = Set<AnyCancellable>()
    
    func fetchUsers() -> AnyPublisher<[User], Error> {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
            return Fail(error: URLError(.badURL))
                .eraseToAnyPublisher()
        }
        
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [User].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
    
    func loadUsers() {
        fetchUsers()
            .receive(on: DispatchQueue.main) // 切换到主线程
            .sink(
                receiveCompletion: { completion in
                    switch completion {
                    case .finished:
                        print("Request completed")
                    case .failure(let error):
                        print("Error: \(error)")
                    }
                },
                receiveValue: { users in
                    print("Received users: \(users)")
                }
            )
            .store(in: &cancellables)
    }
}

定时器示例

swift 复制代码
class TimerExample {
    private var cancellables = Set<AnyCancellable>()
    
    func startTimer() {
        Timer.publish(every: 1.0, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] date in
                print("Timer fired at: \(date)")
                self?.handleTimerTick()
            }
            .store(in: &cancellables)
    }
    
    private func handleTimerTick() {
        // 处理定时器触发
    }
}

内存管理

swift 复制代码
class MyViewController: UIViewController {
    private var cancellables = Set<AnyCancellable>()
    
    deinit {
        // 自动取消所有订阅
        cancellables.forEach { $0.cancel() }
    }
}

最佳实践

  1. 及时取消订阅 :使用 store(in:) 管理订阅生命周期
  2. 线程切换 :使用 receive(on:) 在合适的线程处理数据
  3. 错误处理 :合理使用 catchreplaceError 等操作符
  4. 避免强引用循环 :在闭包中使用 [weak self]

这些是 Combine 的基本使用方法。Combine 提供了强大的响应式编程能力,特别适合处理异步事件流和数据绑定。

相关推荐
Achieve - 前端实验室3 小时前
深入浅出 ES Module
前端·javascript
Lethehong3 小时前
TRAE SOLO:基于React 18+与蓝耘MaaS的多语言智能翻译平台设计与实现
前端·react.js·前端框架·蓝耘元生代·蓝耘maas
技算未来3 小时前
Electron中使用exceljs+Node模块编写
前端
Qinana3 小时前
🚀 用低代码构建AI职业规划应用
前端·程序员·产品
Ebin3 小时前
Shopify 前端实战系列 · S02:Theme 实战进阶
前端
青衫码上行3 小时前
【JavaWeb学习 | 第二篇】CSS(1) - 基础语法与核心概念
前端·css·学习
残冬醉离殇3 小时前
🔥 什么?不用鼠标点击也能触发点击事件???前端工程师的认知塌了!
前端·javascript
重铸码农荣光3 小时前
从 DOM 渲染到代码优雅:ES6 字符串模板与 map 的实战指南
前端·html