如何干净利落地拆分和重组 Swift 发布商值? (使用 OpenCombine)
How can I cleanly split and recombine Swift Publisher values? (using OpenCombine)
- 我有一个上游
Publisher
发射值。例如,十六进制颜色流。
- 另外,我有一个
Operator
由 Publisher
支持,它接受这些值并输出一些关于它们的派生值,从长远来看是一对一的,但需要有关的信息之前或之后的值。例如,return计算每种十六进制颜色与其之前颜色之间的差异。 我不能修改这个。
- 我想要一个简单的方法来 return 这两个值。例如。元组
(color, differenceToPreviousColor)
- 我使用 OpenCombine 是为了向后兼容,它可能缺少一些运算符。例如,它没有
zip
(如果那是我什至需要的)
伪代码:
// Can't mess with this. Basically just returns the consecutive differences
extension Publisher {
func colorDiff<E>() -> AnyPublisher<Color, E> where Color == Output, E == Failure {
return self
.map({ ([=11=], [=11=]) })
.scan((Color.black(), Color.black()), {
let (_, prev) = [=11=]
let (curr, _) =
return (prev, curr)
})
.map({
let (prev, curr) = [=11=]
return curr - prev
})
.eraseToAnyPublisher()
}
}
let colorPublisher = ... // emits #333333; #444444; ...
使用 OpenCombine(不使用 zip)执行如下操作的最简单方法是什么?
let _ = zip(colorPublisher, colorPublisher.colorDiff()).sink { (e) in } receiveValue: { (tuple) in
debugPrint(tuple.description) // (#333333, #333333); (#444444, #111111); ...
}
我不明白你为什么需要 zip
。如果您要开始生成颜色,请捕获每种颜色并将其沿管道 连同您从中生成的颜色差异 传递下去。
这是一个使用数字的粗略模拟,但它向您展示了我的意思:
var storage = Set<AnyCancellable>()
let data = Array(1...100).shuffled()
struct Info {
let this: Int
let diff: Int?
let first: Bool
}
data.publisher
.scan (Info(this: 0, diff: 0, first: true)) { info, this in
Info(this: this, diff: info.first ? nil : this - info.this, first: false)
}
.map {([=10=].this, [=10=].diff)}
.sink { print([=10=]) }
.store(in:&storage)
示例输出:
(80, nil)
(47, Optional(-33))
(35, Optional(-12))
...
我们最终得到一对:这个数字,以及它与前一个数字的差异(表示为可选,因为第一个数字没有前一个数字可以区分,我们需要一种表达方式)。但这些正是 zip 在您想象的场景中会给您的对,不是吗?
我想说的是,“拆分和重组”的整个概念与 Combine 的工作方式相悖。如果在管道早期出现的某些值将需要管道的后期阶段,请继续将其向下传递到中间阶段,以及中间阶段正在处理的任何其他内容。元组或像我的信息这样的载体结构对于此类工作至关重要。
你可以做一个“分裂和重组”,但我无法想象它是如何工作的,除非使用zip
。这是我的示例,重写为使用该方法。使用 Combine 写出来很痛苦,你会注意到我使用了 makeConnectable
和 connect
因为否则我示例中的所有值都会沿着一条路径溢出(因为我的示例实际上不是异步):
let data = Array(1...100).shuffled()
struct Info {
let prev: Int
let diff: Int?
let first: Bool
}
let pub = data.publisher.share().makeConnectable()
let path1 = pub
let path2 = pub
.scan(Info(prev: 0, diff: nil, first: true)) {
Info(prev:, diff: [=12=].first ? nil : - [=12=].prev, first: false)
}
.map {[=12=].diff}
path1
.zip(path2)
.sink {print([=12=])}
.store(in: &storage)
_ = pub.connect()
示例输出:
(39, nil)
(56, Optional(17))
(22, Optional(-34))
...
一个“hacky”解决方法是通过 handleEvents
(OpenCombine 有这个),它允许您拦截在发布者的生命周期内发生的不同事件。
// just a placeholder, giving an initial value to avoid the need for
// an optional, anyway this value will be overwritten by the first
// published value
var currentColor = Color.black()
_ = colorPublisher
// intercept the published value, stored it into a local variable
.handleEvents(receiveOutput: { currentColor = [=10=] })
.colorDiff()
// now combine the latest received color and the diff, into a tuple
.map { (currentColor, [=10=]) }
.sink(receiveValue: { debugPrint([=10=]) })
或者,如果您想捕获最后两项(对于 3,4... 项,您可以扩展元组或使用数组):
var latestColors = (secondLast: Color.black(), last: Color.black())
_ = colorPublisher
.handleEvents(receiveOutput: { latestColors = (latestColors.last, [=11=]) })
.colorDiff()
.map { (latestColors.last, latestColors.secondLast, [=11=]) }
.sink(receiveValue: { debugPrint([=11=]) })
关于 zip
运算符的可用性,OpenCombine 对此有一个 PR - https://github.com/OpenCombine/OpenCombine/pull/109,但该 PR 尚不可合并,但如果您想查看,可以使用该分支运算符实现。
- 我有一个上游
Publisher
发射值。例如,十六进制颜色流。 - 另外,我有一个
Operator
由Publisher
支持,它接受这些值并输出一些关于它们的派生值,从长远来看是一对一的,但需要有关的信息之前或之后的值。例如,return计算每种十六进制颜色与其之前颜色之间的差异。 我不能修改这个。 - 我想要一个简单的方法来 return 这两个值。例如。元组
(color, differenceToPreviousColor)
- 我使用 OpenCombine 是为了向后兼容,它可能缺少一些运算符。例如,它没有
zip
(如果那是我什至需要的)
伪代码:
// Can't mess with this. Basically just returns the consecutive differences
extension Publisher {
func colorDiff<E>() -> AnyPublisher<Color, E> where Color == Output, E == Failure {
return self
.map({ ([=11=], [=11=]) })
.scan((Color.black(), Color.black()), {
let (_, prev) = [=11=]
let (curr, _) =
return (prev, curr)
})
.map({
let (prev, curr) = [=11=]
return curr - prev
})
.eraseToAnyPublisher()
}
}
let colorPublisher = ... // emits #333333; #444444; ...
使用 OpenCombine(不使用 zip)执行如下操作的最简单方法是什么?
let _ = zip(colorPublisher, colorPublisher.colorDiff()).sink { (e) in } receiveValue: { (tuple) in
debugPrint(tuple.description) // (#333333, #333333); (#444444, #111111); ...
}
我不明白你为什么需要 zip
。如果您要开始生成颜色,请捕获每种颜色并将其沿管道 连同您从中生成的颜色差异 传递下去。
这是一个使用数字的粗略模拟,但它向您展示了我的意思:
var storage = Set<AnyCancellable>()
let data = Array(1...100).shuffled()
struct Info {
let this: Int
let diff: Int?
let first: Bool
}
data.publisher
.scan (Info(this: 0, diff: 0, first: true)) { info, this in
Info(this: this, diff: info.first ? nil : this - info.this, first: false)
}
.map {([=10=].this, [=10=].diff)}
.sink { print([=10=]) }
.store(in:&storage)
示例输出:
(80, nil)
(47, Optional(-33))
(35, Optional(-12))
...
我们最终得到一对:这个数字,以及它与前一个数字的差异(表示为可选,因为第一个数字没有前一个数字可以区分,我们需要一种表达方式)。但这些正是 zip 在您想象的场景中会给您的对,不是吗?
我想说的是,“拆分和重组”的整个概念与 Combine 的工作方式相悖。如果在管道早期出现的某些值将需要管道的后期阶段,请继续将其向下传递到中间阶段,以及中间阶段正在处理的任何其他内容。元组或像我的信息这样的载体结构对于此类工作至关重要。
你可以做一个“分裂和重组”,但我无法想象它是如何工作的,除非使用zip
。这是我的示例,重写为使用该方法。使用 Combine 写出来很痛苦,你会注意到我使用了 makeConnectable
和 connect
因为否则我示例中的所有值都会沿着一条路径溢出(因为我的示例实际上不是异步):
let data = Array(1...100).shuffled()
struct Info {
let prev: Int
let diff: Int?
let first: Bool
}
let pub = data.publisher.share().makeConnectable()
let path1 = pub
let path2 = pub
.scan(Info(prev: 0, diff: nil, first: true)) {
Info(prev:, diff: [=12=].first ? nil : - [=12=].prev, first: false)
}
.map {[=12=].diff}
path1
.zip(path2)
.sink {print([=12=])}
.store(in: &storage)
_ = pub.connect()
示例输出:
(39, nil)
(56, Optional(17))
(22, Optional(-34))
...
一个“hacky”解决方法是通过 handleEvents
(OpenCombine 有这个),它允许您拦截在发布者的生命周期内发生的不同事件。
// just a placeholder, giving an initial value to avoid the need for
// an optional, anyway this value will be overwritten by the first
// published value
var currentColor = Color.black()
_ = colorPublisher
// intercept the published value, stored it into a local variable
.handleEvents(receiveOutput: { currentColor = [=10=] })
.colorDiff()
// now combine the latest received color and the diff, into a tuple
.map { (currentColor, [=10=]) }
.sink(receiveValue: { debugPrint([=10=]) })
或者,如果您想捕获最后两项(对于 3,4... 项,您可以扩展元组或使用数组):
var latestColors = (secondLast: Color.black(), last: Color.black())
_ = colorPublisher
.handleEvents(receiveOutput: { latestColors = (latestColors.last, [=11=]) })
.colorDiff()
.map { (latestColors.last, latestColors.secondLast, [=11=]) }
.sink(receiveValue: { debugPrint([=11=]) })
关于 zip
运算符的可用性,OpenCombine 对此有一个 PR - https://github.com/OpenCombine/OpenCombine/pull/109,但该 PR 尚不可合并,但如果您想查看,可以使用该分支运算符实现。