Apple处理异步任务的Combine框架
Apple处理异步任务的Combine框架

Apple处理异步任务的Combine框架

Combine 是 Apple 推出的一个框架,用于处理异步事件和响应式编程。它可以帮助开发者以声明性的方式处理复杂的异步任务,例如网络请求、定时器、UI 更新等。

核心概念

1、Publisher(发布者)

提供数据流,可以是单个值或多个值。

代表事件的来源,比如网络请求结果、用户输入、定时器触发等。

常见的内置 Publisher

1)Just:发布一个单一值。

Just 是一种特殊的 Publisher,它只发布一个单一的值,然后立即完成。

适用于需要立即返回一个简单值的场景。

特点

只能发布一个值。

一旦创建,就不可修改。

发布后立即完成订阅。

import Combine

let justPublisher = Just(42)

let subscription = justPublisher.sink(
    receiveCompletion: { completion in
        print("Completion: \(completion)") // 打印 "Completion: finished"
    },
    receiveValue: { value in
        print("Value: \(value)") // 打印 "Value: 42"
    }
)

// 输出:
// Value: 42
// Completion: finished

2)PassthroughSubject:动态发布多个值。

它不会保留任何先前发送的值,新的订阅者只会收到订阅之后的值。

常用于处理用户交互、通知或其他需要即时传播的事件。

特点

没有初始值。

只会发布订阅后产生的值。

手动调用 .send(_:) 发布数据。

import Combine

let subject = PassthroughSubject<String, Never>()

let subscription = subject.sink(
    receiveCompletion: { completion in
        print("Completion: \(completion)")
    },
    receiveValue: { value in
        print("Value: \(value)")
    }
)

subject.send("Hello") // 打印 "Value: Hello"
subject.send("World") // 打印 "Value: World"
subject.send(completion: .finished) // 打印 "Completion: finished"

3)CurrentValueSubject:持有一个当前值,并可以发布新的值。

每次订阅时,订阅者都会收到当前值,然后再接收之后的值。

常用于状态管理或需要持有某个值的场景。

特点

持有一个初始值。

任何订阅者都会先收到当前值。

手动调用 .send(_:) 更新值。

import Combine

let currentValueSubject = CurrentValueSubject<Int, Never>(10)

let subscription1 = currentValueSubject.sink(
    receiveCompletion: { completion in
        print("Completion: \(completion)")
    },
    receiveValue: { value in
        print("Subscription1 Value: \(value)")
    }
)

currentValueSubject.send(20) // Subscription1 收到 20

let subscription2 = currentValueSubject.sink(
    receiveValue: { value in
        print("Subscription2 Value: \(value)")
    }
)

// Subscription2 会先收到当前值 20
currentValueSubject.send(30) // 两个订阅者都收到 30

// 输出:
// Subscription1 Value: 10
// Subscription1 Value: 20
// Subscription2 Value: 20
// Subscription1 Value: 30
// Subscription2 Value: 30

2、Subscriber(订阅者)

订阅 Publisher,接收其发布的数据。

开发者可以实现自己的订阅者,或者使用 Combine 提供的内置方法,例如 .sink 或 .assign。

3、Operator(操作符)

用于对数据流进行变换、过滤、合并等操作。

常用操作符

map:将一个值映射为另一个值。

filter:筛选满足条件的数据。

combineLatest:合并多个 Publisher 的最新值。

flatMap:将一个 Publisher 的值映射为另一个 Publisher。

4、Cancellable(取消任务)

订阅后返回的对象,可以通过调用 .cancel() 停止订阅,避免内存泄漏。

5、Subjects(主题)

既是 Publisher,也是 Subscriber。

可用于手动触发值的发布。

PassthroughSubject:初始没有值,仅发送接收到的值。

CurrentValueSubject:保存当前值,并向新订阅者发送最新值。

简单示例:数据流处理

import Combine

// 1. 创建一个 Publisher
let numbers = [1, 2, 3, 4, 5].publisher

// 2. 使用 Operator 转换数据
let subscription = numbers
    .filter { $0 % 2 == 0 }  // 只保留偶数
    .map { $0 * 10 }         // 将每个值乘以 10
    .sink { value in
        print("Received value: \(value)")
    }

// 输出:
// Received value: 20
// Received value: 40

与 SwiftUI 集成:@Published 和 @StateObject

在 MVVM 架构中,Combine 常用于连接 ViewModel 和 View。SwiftUI 提供了便捷的 @Published 和 @StateObject,简化了响应式编程。

示例:ViewModel 与 View 的绑定

import SwiftUI
import Combine

// ViewModel
class CounterViewModel: ObservableObject {
    @Published var count: Int = 0

    func increment() {
        count += 1
    }
}

// View
struct CounterView: View {
    @StateObject private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("Count: \(viewModel.count)")
                .font(.largeTitle)
            Button("Increment") {
                viewModel.increment()
            }
            .padding()
        }
    }
}

高级用法:处理网络请求

通过 Combine,可以优雅地处理网络请求并将数据绑定到 UI。

示例:网络请求 + SwiftUI

import SwiftUI
import Combine

class APIViewModel: ObservableObject {
    @Published var data: String = "Loading..."
    private var cancellable: AnyCancellable?

    func fetchData() {
        let url = URL(string: "https://api.example.com/data")!
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map { String(data: $0.data, encoding: .utf8) ?? "No Data" }
            .replaceError(with: "Error fetching data")
            .receive(on: DispatchQueue.main)
            .sink { [weak self] in self?.data = $0 }
    }
}

struct APIView: View {
    @StateObject private var viewModel = APIViewModel()

    var body: some View {
        VStack {
            Text(viewModel.data)
                .padding()
            Button("Fetch Data") {
                viewModel.fetchData()
            }
        }
        .onAppear {
            viewModel.fetchData()
        }
    }
}

优点

1、声明式编程:代码可读性高,逻辑清晰。

2、易于组合:通过操作符轻松处理数据流的复杂逻辑。

3、线程管理:通过 .receive(on:) 控制数据在哪个线程中处理。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注