You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift Combine:收集条件成立值并包含触发终止的目标值

解决Swift Combine中prefix操作符丢失终止触发元素的问题

prefix(while:)的核心逻辑是仅传递满足条件的元素,当遇到第一个不满足条件的元素时,立即终止序列且不会传递该元素。这就是你代码中"LastPage"未被收集的原因——它触发了终止条件,但本身没有被下游接收。


解决方案1:使用scan+prefix+map组合处理

直接通过scan跟踪序列状态,确保触发终止的元素被包含在内:

import Foundation
import Combine

var subj = PassthroughSubject<String, Never>()

let cancel = subj
    // 用scan跟踪当前元素和是否继续接收后续元素的状态
    .scan((element: String(), shouldContinue: true)) { prev, current in
        let shouldContinue = prev.shouldContinue && (current != "LastPage")
        return (element: current, shouldContinue: shouldContinue)
    }
    // 保留所有元素直到:要么仍在继续状态,要么当前是触发终止的元素
    .prefix { $0.shouldContinue || $0.element == "LastPage" }
    // 提取实际元素,去掉状态标记
    .map { $0.element }
    .collect(.byTime(DispatchQueue(label: "Test"), .seconds(3)))
    .sink {
        print("complete: \($0)")
    } receiveValue: {
        print("received: \($0)")
    }

print("start")

let strings = [
    "!@#$",
    "ZXCV",
    "LastPage",
    "ASDF",
    "JKL:"
]

for i in (0..<strings.count) {
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(i)) {
        let s = strings[i]
        print("sending \(s)")
        subj.send(s)
    }
}

输出结果:

start
sending !@#$
sending ZXCV
sending LastPage
received: ["!@#$", "ZXCV", "LastPage"]
complete: finished
sending ASDF

解决方案2:自定义可复用的prefixIncludingWhile操作符

如果需要在多个地方复用这个逻辑,可以自定义一个操作符,封装上述逻辑:

import Foundation
import Combine

// 自定义Publisher,实现包含终止触发元素的prefix逻辑
extension Publishers {
    struct PrefixIncludingWhile<Upstream: Publisher>: Publisher {
        typealias Output = Upstream.Output
        typealias Failure = Upstream.Failure
        
        private let upstream: Upstream
        private let predicate: (Upstream.Output) -> Bool
        
        init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
            self.upstream = upstream
            self.predicate = predicate
        }
        
        func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
            upstream
                .scan((element: Output?.none, shouldContinue: true)) { prev, current in
                    let shouldContinue = prev.shouldContinue && self.predicate(current)
                    return (element: current, shouldContinue: shouldContinue)
                }
                .prefix { $0.shouldContinue || !self.predicate($0.element!) }
                .map { $0.element! }
                .receive(subscriber: subscriber)
        }
    }
}

// 给Publisher扩展便捷调用方法
extension Publisher {
    func prefixIncludingWhile(_ predicate: @escaping (Output) -> Bool) -> Publishers.PrefixIncludingWhile<Self> {
        Publishers.PrefixIncludingWhile(upstream: self, predicate: predicate)
    }
}

// 使用示例
var subj = PassthroughSubject<String, Never>()

let cancel = subj
    .prefixIncludingWhile { $0 != "LastPage" }
    .collect(.byTime(DispatchQueue(label: "Test"), .seconds(3)))
    .sink {
        print("complete: \($0)")
    } receiveValue: {
        print("received: \($0)")
    }

print("start")

let strings = [
    "!@#$",
    "ZXCV",
    "LastPage",
    "ASDF",
    "JKL:"
]

for i in (0..<strings.count) {
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(i)) {
        let s = strings[i]
        print("sending \(s)")
        subj.send(s)
    }
}

这个自定义操作符的用法和原生prefix(while:)几乎一致,但会自动包含触发终止条件的那个元素。


内容的提问来源于stack exchange,提问作者Yogurt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:10:21