JHHK

欢迎来到我的个人网站
行者常至 为者常成

Combine 1

目录

介绍

一、是什么

Combine 是 Apple 在 iOS 13 / macOS 10.15 引入的一个 响应式编程框架。
提供了一个统一的的数据流处理模型, Publisher → Operator → Subscriber

二、解决了什么问题

KVO / NotificationCenter / Delegate
各种机制分散在不同 API,缺乏统一抽象,使用combine框架就可以不使用上述方案了。

UI 与数据绑定
数据变化时自动更新 UI

回调地狱(callback hell)
异步操作嵌套多层回调,层层嵌套,难以维护。
比如一个请求依赖另一个请求时,会有两层嵌套。

异步任务的组合
比如要同时发起两个网络请求,等两个都完成后再处理。

三、核心原理是什么

xy: combine 是一个 发布-订阅 模式的 流模型,数据从publisher 流向 operator 最后到达 subscriber,
publisher作为数据源,
operator对数据进行过滤或转换,
subscriber对数据进行消费

Combine 的核心思想是 响应式流模型(Reactive Streams),用“发布-订阅”模式(Publisher-Subscriber Pattern)。

原理关键点:

Publisher (发布者)
定义了可以异步发出的一系列值(包括正常值、完成事件、错误)。
类似一个“数据源”。

Operator (操作符)
连接 Publisher 和 Subscriber。
通过链式调用,组合/转换数据流。

Subscriber (订阅者)
订阅后才会触发数据流。
负责接收 Publisher 发出的值和完成/错误事件。

背压机制 (Backpressure)
Subscriber 可以请求数据的数量,避免 Publisher 过快产生数据导致资源耗尽。

基于 Swift 泛型 + 协议
Publisher 和 Subscriber 都是协议。
Publisher 有 Output 和 Failure 两个关联类型,用于强类型约束。

Publisher

一、@Published

class ViewModel {
    
    @Published
    var text = "this is text"
}
// @Published 修饰的String的订阅,只要text的值发生变化就会触发订阅    
vm.$text.sink { complete in
    switch complete {
    case .finished :
        print("$text finished")
    case .failure(let error):
        print("$text failure:\(error)")
    }
} receiveValue: { value in
    print(value)
}.store(in: &cancellables)

二、Subject

1、PassthroughSubject

class ViewModel {
    let passthroughSubjectPub = PassthroughSubject<String, Never>()
}

passthroughSubject订阅时不会触发回调

// passthroughSubject 的订阅
vm.passthroughSubjectPub.sink { complete in
    switch complete {
    case .finished :
        print("passthroughSubject finished")
    case .failure(let error):
        print("passthroughSubject failure:\(error)")
    }
} receiveValue: { value in
    print(value)
}.store(in: &cancellables)

PassthroughSubject:只转发手动发送的值,不保存历史。

 vm.passthroughSubjectPub.send("passthrough")

2、CurrentValueSubject

CurrentValueSubject:和 PassthroughSubject 类似,但会保留最近一个值

class ViewModel {
    let currentValueSubjectPub = CurrentValueSubject<String, Never>("currentValue")
}

CurrentValueSubject订阅时会触发回调,返回当前值

// currentValueSubject 的订阅
vm.currentValueSubjectPub.sink { complete in
    switch complete {
    case .finished :
        print("currentValueSubject finished")
    case .failure(let error):
        print("currentValueSubject failure:\(error)")
    }
} receiveValue: { value in
    print(value)
}.store(in: &cancellables)
vm.currentValueSubjectPub.send("currentValue")

三、订阅后立即发出

class ViewModel {
    // 下面这些只要订阅就会立即收到结果
    let justPub = Just("just")
    let emptyPub = Empty<Int,Never>()
    let failPub = Fail<Int, MyError>(error: .somethingWrong)
    let sequencePub = [1, 2, 3].publisher
}

Just:立即发出一个值并完成。只要订阅就会立即收到Just发出的值

vm.justPub.sink { complete in
    switch complete {
    case .finished :YI
        print("justPub finished")
    case .failure(let error):
        print("justPub failure:\(error)")
    }
} receiveValue: { value in
    print(value)
}.store(in: &cancellables)

Empty:不发任何值,直接完成。只要订阅就会立即完成

vm.emptyPub.sink { complete in
    switch complete {
    case .finished :
        print("emptyPub finished")
    case .failure(let error):
        print("emptyPub failure:\(error)")
    }
} receiveValue: { value in
    print(value)
}.store(in: &cancellables)

Operator

一、filter

用于过滤数据

[10,20,30,40,50].publisher
    .filter({ value in
        // 大于20的数据返回,小于等于20的数据被过滤掉
        return value > 20
    })
    .sink { complete in
        switch complete {
            
        case .finished:
            print("finished")
            
        case .failure(let error):
            print("fail \(error)")
        }
    } receiveValue: { value in
        print(value)
    }.store(in: &cancellables)

二、map

用于映射数据,或数据类型转换

static func fetchHomeLoginLazyPublisher(param: [String : Any]) -> AnyPublisher<HomeLogin?,NetworkError> {
    return Request().fetchDataLazyPublisher(
        urlStr: "https://mock.apipost.net/mock/51814f4a3871000/home/login",
        httpMethod: "POST",
        parameters: param
    ).map({(responseData:ResponseData<HomeLogin>) in
        responseData.data
    }).eraseToAnyPublisher()
}

三、flatMap

链式请求

HomeRequest.fetchHomeOnceLazyPublisher()
    .map{ homeOnce in
        print("homeOnce is \(homeOnce, default: "")")
        return homeOnce?.once
    }
    .flatMap { once in
        let param: [String : Any] = [
            "once":once ?? "",
            "username":"jianghuhike",
            "password":"123456"
        ]
        return HomeRequest.fetchHomeLoginLazyPublisher(param: param)
    }
    .sink { complete in
        switch complete {
        case .failure(let error):
            print("捕获的错误:\(error)")
        case .finished:
            print("finish")
        }
    } receiveValue: { homeDes in
        print(homeDes ?? "data is nil")
    }
    .store(in: &cancellables)

并发控制

let userIds = [1, 2, 3, 4, 5]
userIds.publisher
    .flatMap(maxPublishers: .max(2)) { id in
        print("user id is \(id)")
        return Just("user document \(id)") // 并行请求每个用户的文章
    }
    .collect()  // 等所有完成后收集为 [Post]
    .sink(receiveCompletion: { print($0) },
          receiveValue: { allPosts in
              print("总共获取到 \(allPosts) ")
          })
    .store(in: &cancellables)

四、节流

Timer.publish(every: 1.0, on: .main, in: .default)
    .autoconnect()
    .print("打印日期:\(Date().description)")
    .throttle(for: 2.0, scheduler: RunLoop.main, latest: true)
    .sink(
        receiveCompletion: { print ("Completion: \($0).") },
        receiveValue: { print("Received Timestamp \($0).",terminator: "\n---------------------\n") }
    )
    .store(in: &cancellables)

节流:滚动位置上报

scrollSubject
    .map { $0.y }
    .removeDuplicates()
    .throttle(for: .seconds(1), scheduler: RunLoop.main, latest: true)
    .sink { y in
        print("📡 (subject) 滚动位置上报: \(y)")
    }
    .store(in: &cancellables)

五、防抖

防抖:搜索框输入时,延迟0.5秒后才发起请求

NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: textField)
    .compactMap { ($0.object as? UITextField)?.text }
    .debounce(for: .milliseconds(500), scheduler: RunLoop.main) // 防抖:0.5 秒
    .removeDuplicates() // 去重:避免相同输入重复触发
    .sink { [weak self] text in
        print("🔍 开始搜索: \(text)")
        self?.search(keyword: text)
    }
    .store(in: &cancellables)

Subscriber

一、sink

只接收完成后的值

[1, 2, 3].publisher
    .sink { value in
        print("value is \(value)")
    }
    .store(in: &cancellables)

接收完成事件和完成后的值

[1,2,3].publisher
    .sink { complete in
        switch complete {
        case .finished:
            print("finieshe")
        case .failure(let error):
            print("error is \(error)")
        }
    } receiveValue: { value in
        print("value is \(value)")
    }
    .store(in: &cancellables)

二、assign

数据模型绑定

NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameField)
    .map { ($0.object as? UITextField)?.text ?? "" }
    .assign(to: \.userName, on: self)
    .store(in: &cancellables)


NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordField)
    .map { ($0.object as? UITextField)?.text ?? "" }
    .assign(to: \.password, on: self)
    .store(in: &cancellables)

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.