首页 > 解决方案 > 递归发送值到 combineLatest 发布者

问题描述

我在 Combine 中遇到了一些意想不到的行为,我希望有人能够解释。我希望下面的代码创建一个无限循环,但它实际上只在流中运行一次。

let pub1 = PassthroughSubject<Int, Never>()
let pub2 = PassthroughSubject<Int, Never>()

pub1
    .handleEvents(receiveOutput: { print("Received new pub1 value: \($0)") })
    .combineLatest(pub2)
    .handleEvents(receiveOutput: { print("Received new combined value: \($0)") })
    .sink { value in
        print(value)
        pub1.send(value.0)
    }.store(in: &subscriptions)

print("sending 1")
pub1.send(1)
print("sending 2")
pub2.send(2)

生成以下输出:

Received new pub1 value: 1
sending 2
Received new combined value: (1, 2)
(1, 2)
Received new pub1 value: 1

由于 pub1 内部的值会反馈给自身,因此我希望 sink 会被一遍又一遍地调用。有趣的是,如果我去掉 combineLatest ,那么这段代码将创建一个无限循环。关于 combineLatest 运算符的某些东西正在阻止它,我不知道是什么。

我还注意到.receive(on: DispatchQueue.main)在之前或之后添加combineLatest也会触发循环。我想我不了解Combine中如何处理线程。我没有看到其他运营商的这种非循环行为。例如merge(with:),正如预期的那样,还将创建一个循环。

标签: swiftmultithreadingcombinecombinelatest

解决方案


您注意到的行为是由CombineLatest运算符的实现细节引起的。在 Combine 框架的二进制文件中进行一些低级调试显示了以下特定指令:

拆卸

第 95 行是事情不同的地方,当进行pub1.send(1)andpub2.send(2)调用时,执行将继续执行下一条指令(第 96 行),而闭包send()内的调用通过了测试,并且执行跳转到函数的末尾。sinkjne

调用堆栈如下所示:

在此处输入图像描述

似乎CombineLatest实现要么有一些防止递归的保护措施,要么这是另一个实现细节的副作用。

请注意,内存地址和确切指令可能取决于构建可执行文件的计算机,但想法保持不变。


推荐阅读