【IOS】Combine


在这里插入图片描述

一、Combine 是什么?

Combine 是 Apple 在 WWDC 2019 推出的声明式响应式编程框架,用于处理异步事件和数据流。它的核心思想是:将随时间变化的值(网络请求、UI 输入、定时器等)建模为事件流,通过操作符进行组合和转换

简单说,Combine 让你用声明式的方式描述"当数据变化时做什么",而不是用嵌套闭包手动管理回调。

前置知识:Combine 大量使用 Swift 的泛型等高级特性,建议具备中级 Swift 水平再学习。


二、核心概念(三要素)

Combine 的核心围绕三个角色展开:

概念作用类比
Publisher(发布者)事件的源头,产出值/错误/完成信号报社
Operator(操作符)处理站,转换/过滤/组合数据编辑部
Subscriber(订阅者)事件的终点,接收并消费数据订户

数据流向:Publisher → (零个或多个 Operator) → Subscriber

2.1 Publisher(发布者)

Publisher 可以发出三种东西:

  • 值(Values):你关心的数据
  • 完成(Completion).finished 表示正常结束
  • 失败(Failure).failure(error) 表示因错误终止

Publisher 在没有被订阅前什么都不会做

常用内置 Publisher:

  • Just:发一个值后立即完成
  • Future:异步产生一个结果(成功或失败)
  • PassthroughSubject:手动发送值,不保留历史
  • CurrentValueSubject:手动发送值,保存当前值(新订阅者立即收到最新值)
  • @Published:属性包装器,将属性变成 Publisher(SwiftUI 中极常用)

2.2 Operator(操作符)

操作符是 Combine 强大灵活的关键,它们接收上游事件、处理后发给下游,每个操作符返回一个新的 Publisher,支持链式调用。

常用操作符分类:

类别操作符作用
转换mapflatMapscan转换数据类型或结构
过滤filtercompactMapremoveDuplicates按条件筛选数据
合并mergezipcombineLatest组合多个数据流
时间控制debouncethrottledelay控制事件发送时机

2.3 Subscriber(订阅者)

Subscriber 消费数据。Combine 提供两个内置订阅者:

  • sink:最通用,接收两个闭包——处理值的 receiveValue 和处理完成的 receiveCompletion
  • assign:将接收到的值直接赋值给某个对象的属性(如 UI 控件的 text

三、基础用法(代码示例)

3.1 创建 Publisher 并订阅

import Combine

// 1. 创建 Publisher(发布者)
let publisher = [1, 2, 3, 4, 5].publisher

// 2. 使用 Operator 处理数据
// 3. 使用 Subscriber 订阅
let cancellable = publisher
    .filter { $0 % 2 == 0 }      // 只保留偶数
    .map { $0 * $0 }             // 平方
    .sink(
        receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("流正常结束")
            case .failure(let error):
                print("流因错误结束: \(error)")
            }
        },
        receiveValue: { value in
            print("接收到值: \(value)")  // 输出: 4, 16
        }
    )

3.2 使用 Just + sink

let publisher = Just("Hello, Combine!")

publisher
    .map { $0.uppercased() }
    .sink { value in
        print(value)  // 输出: HELLO, COMBINE!
    }

3.3 使用 PassthroughSubject 手动发送事件

let subject = PassthroughSubject<String, Never>()

let subscription = subject
    .sink { value in
        print("收到: \(value)")
    }

subject.send("Hello")   // 输出: 收到: Hello
subject.send("Combine") // 输出: 收到: Combine
subject.send(completion: .finished) // 发送完成信号

3.4 使用 assign 绑定到 UI

class MyViewModel {
    @Published var username: String = ""
}

class MyViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    var cancellables = Set<AnyCancellable>()
    let viewModel = MyViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.$username
            .assign(to: \.text, on: nameLabel)
            .store(in: &cancellables)  // 存储订阅,防止提前释放
    }
}

关键点store(in: &cancellables) 用于管理订阅的生命周期,避免内存泄漏。


四、进阶主题

4.1 网络请求(URLSession.dataTaskPublisher

struct Post: Decodable {
    let id: Int
    let title: String
}

func fetchPosts() -> AnyPublisher<[Post], Error> {
    let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
    return URLSession.shared.dataTaskPublisher(for: url)
        .map { $0.data }
        .decode(type: [Post].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()  // 类型擦除,隐藏具体 Publisher 类型
}

4.2 使用 Future 替代传统闭包回调

传统写法:

func performAsyncAction(completionHandler: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        completionHandler()
    }
}

Combine 写法:

func performAsyncActionAsFuture() -> Future<Void, Never> {
    return Future { promise in
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            promise(.success(()))
        }
    }
}

// 使用
performAsyncActionAsFuture()
    .sink { _ in print("Future 完成") }

4.3 防抖(Debounce)—— 搜索框实战

class SearchViewModel {
    @Published var searchText = ""
    var cancellables = Set<AnyCancellable>()
    
    init() {
        $searchText
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { text in
                print("执行搜索: \(text)")  // 用户停止输入 500ms 后才触发
            }
            .store(in: &cancellables)
    }
}

4.4 合并多个 Publisher

let publisher1 = Just(1)
let publisher2 = Just(2)
let publisher3 = Just(3)

publisher1.merge(with: publisher2, publisher3)
    .sink { print($0) }  // 输出: 1, 2, 3(顺序不确定)

4.5 调度器(Scheduler)—— 控制线程

URLSession.shared.dataTaskPublisher(for: url)
    .subscribe(on: DispatchQueue.global())  // 在后台线程执行网络请求
    .receive(on: DispatchQueue.main)        // 在主线程更新 UI
    .sink(...)

五、总结

学习阶段目标
入门理解 Publisher、Operator、Subscriber 三要素,能写简单的 sink/assign 订阅
进阶掌握常用操作符(map/filter/debounce/merge),会用 URLSession 网络请求
实战结合 SwiftUI(@Published + ObservableObject)构建响应式应用,处理复杂异步场景

Combine 是 SwiftUI 数据驱动 UI 的基石,掌握它之后,你的异步代码将更加清晰、可维护、可组合。建议从简单的 JustPassthroughSubject 开始动手练习,逐步深入到网络请求和 UI 绑定场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软泡芙

给爷鞠躬!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值