Swift Combine:如何创建可重复使用的 Publishers.Map 以连接到多个上游发布者?
Swift Combine: How can I create a reusable Publishers.Map to connect to multiple upstream Publishers?
我正试图全神贯注于 Combine,当我重构代码时,当我尝试重新组合以避免重复自己时,我 运行 陷入了一些混乱。
在这种情况下,我有一个值我想随时更新:
- 特定主题发生变化
- 应用进入前台
- 触发 3 秒刷新计时器
由于 3 秒刷新计时器在第一次触发之前不会发布任何内容,因此我假设我需要多个发布者。
我总是只使用来自主题的值,而忽略从前台通知和计时器发送的任何值。
这是一些示例代码,我在其中处理仅基于一个发布者的值:
import UIKit
import Combine
class DataStore {
@Published var fillPercent: CGFloat = 0
private var cancellables: [AnyCancellable] = []
// how much the glass can hold, in milliliters
private let glassCapacity: Double = 500
// how full the glass is, in milliliters
private var glassFillLevelSubject = CurrentValueSubject<Double,Never>(250)
// a publisher that fires every three seconds
private let threeSecondTimer = Timer
.publish(every: 3,
on: RunLoop.main,
in: .common)
.autoconnect()
// a publisher that fires every time the app enters the foreground
private let willEnterForegroundPublisher = NotificationCenter.default
.publisher(for: UIApplication.willEnterForegroundNotification)
init() {
// publisher that fires any time the glass level changes or three second timer fires
let glassLevelOrTimerPublisher = Publishers.CombineLatest(glassFillLevelSubject, threeSecondTimer)
// is there shorthand to only return the first item? like .map{ [=12=] }?
.map { glassFillLevel, timer -> Double in
return glassFillLevel
}
.eraseToAnyPublisher()
// publisher that fires any time the glass level changes or three second timer fires
let glassLevelOrForegroundPublisher = Publishers.CombineLatest(glassFillLevelSubject, willEnterForegroundPublisher)
.map{ glassFillLevel, notification -> Double in
return glassFillLevel
}
.eraseToAnyPublisher()
// how can I define map and everything after it as something, and then subscribe it to the two publishers above?
glassLevelOrTimerPublisher
.map{ fillLevelInMilliliters in
let fillPercent = fillLevelInMilliliters / self.glassCapacity
return CGFloat(fillPercent)
}
.assign(to: \.fillPercent, on: self)
.store(in: &cancellables)
}
}
我想我想在这里做的是以某种方式分离出 .map
和它后面的所有内容,并以某种方式将其订阅给上面的两个发布者。
我试过这个,作为隔离 .map
之后的所有内容以使其可重用的方法:
let fillPercentStream = Publishers.Map{ fillLevelInMilliliters in
let fillPercent = fillLevelInMilliliters / self.glassCapacity
return CGFloat(fillPercent)
}
.assign(to: \.fillPercent, on: self)
.store(in: &cancellables)
但这给了我一个错误 Missing argument for parameter 'upstream' in call
所以我尝试为该参数添加一些东西,结果是这样的:
let fillPercentStream = Publishers.Map(upstream: AnyPublisher<Double,Never>, transform: { fillLevelInMilliliters in
let fillPercent = fillLevelInMilliliters / self.glassCapacity
return CGFloat(fillPercent)
})
.assign(to: \.fillPercent, on: self)
.store(in: &cancellables)
然后,我遇到了一系列编译器错误:Unable to infer complex closure return type; add explicit type to disambiguate
它建议我在 .map
中指定 -> CGFloat
,我添加了它,但后来它告诉我应该将 CGFloat
更改为 _
,我最终会遇到更多错误。
这甚至是我应该能够用 Combine 做的事情吗?我会以错误的方式处理这件事吗?我如何正确地重新使用两个不同发布者的 .map
和 .assign
链?
总的来说,我对 Combine 和响应式编程有些陌生,所以如果您有其他建议来改进我在这里做的所有事情,请告诉我。
处理此问题的一种方法是将您的计时器发布者输出映射到主题的值,并将您的通知发布者输出映射到主题的值。然后,您可以将主题、计时器和通知合并到一个发布者中,该发布者在主题的值更改时、计时器触发时以及通知发布时发布主题的值。
import Combine
import Foundation
import UIKit
class DataStore: ObservableObject {
init() {
fractionFilled = CGFloat(fillSubject.value / capacity)
let fillSubject = self.fillSubject // avoid retain cycles in map closures
let timer = Timer.publish(every: 3, on: .main, in: .common)
.autoconnect()
.map { _ in fillSubject.value }
let foreground = NotificationCenter.default
.publisher(for: UIApplication.willEnterForegroundNotification)
.map { _ in fillSubject.value }
let combo = fillSubject.merge(with: timer, foreground)
combo
.map { [capacity] in CGFloat([=10=] / capacity) }
.sink { [weak self] in self?.fractionFilled = [=10=] }
.store(in: &tickets)
}
let capacity: Double = 500
@Published var fractionFilled: CGFloat
private let fillSubject = CurrentValueSubject<Double, Never>(250)
private var tickets: [AnyCancellable] = []
}
我正试图全神贯注于 Combine,当我重构代码时,当我尝试重新组合以避免重复自己时,我 运行 陷入了一些混乱。
在这种情况下,我有一个值我想随时更新:
- 特定主题发生变化
- 应用进入前台
- 触发 3 秒刷新计时器
由于 3 秒刷新计时器在第一次触发之前不会发布任何内容,因此我假设我需要多个发布者。
我总是只使用来自主题的值,而忽略从前台通知和计时器发送的任何值。
这是一些示例代码,我在其中处理仅基于一个发布者的值:
import UIKit
import Combine
class DataStore {
@Published var fillPercent: CGFloat = 0
private var cancellables: [AnyCancellable] = []
// how much the glass can hold, in milliliters
private let glassCapacity: Double = 500
// how full the glass is, in milliliters
private var glassFillLevelSubject = CurrentValueSubject<Double,Never>(250)
// a publisher that fires every three seconds
private let threeSecondTimer = Timer
.publish(every: 3,
on: RunLoop.main,
in: .common)
.autoconnect()
// a publisher that fires every time the app enters the foreground
private let willEnterForegroundPublisher = NotificationCenter.default
.publisher(for: UIApplication.willEnterForegroundNotification)
init() {
// publisher that fires any time the glass level changes or three second timer fires
let glassLevelOrTimerPublisher = Publishers.CombineLatest(glassFillLevelSubject, threeSecondTimer)
// is there shorthand to only return the first item? like .map{ [=12=] }?
.map { glassFillLevel, timer -> Double in
return glassFillLevel
}
.eraseToAnyPublisher()
// publisher that fires any time the glass level changes or three second timer fires
let glassLevelOrForegroundPublisher = Publishers.CombineLatest(glassFillLevelSubject, willEnterForegroundPublisher)
.map{ glassFillLevel, notification -> Double in
return glassFillLevel
}
.eraseToAnyPublisher()
// how can I define map and everything after it as something, and then subscribe it to the two publishers above?
glassLevelOrTimerPublisher
.map{ fillLevelInMilliliters in
let fillPercent = fillLevelInMilliliters / self.glassCapacity
return CGFloat(fillPercent)
}
.assign(to: \.fillPercent, on: self)
.store(in: &cancellables)
}
}
我想我想在这里做的是以某种方式分离出 .map
和它后面的所有内容,并以某种方式将其订阅给上面的两个发布者。
我试过这个,作为隔离 .map
之后的所有内容以使其可重用的方法:
let fillPercentStream = Publishers.Map{ fillLevelInMilliliters in
let fillPercent = fillLevelInMilliliters / self.glassCapacity
return CGFloat(fillPercent)
}
.assign(to: \.fillPercent, on: self)
.store(in: &cancellables)
但这给了我一个错误 Missing argument for parameter 'upstream' in call
所以我尝试为该参数添加一些东西,结果是这样的:
let fillPercentStream = Publishers.Map(upstream: AnyPublisher<Double,Never>, transform: { fillLevelInMilliliters in
let fillPercent = fillLevelInMilliliters / self.glassCapacity
return CGFloat(fillPercent)
})
.assign(to: \.fillPercent, on: self)
.store(in: &cancellables)
然后,我遇到了一系列编译器错误:Unable to infer complex closure return type; add explicit type to disambiguate
它建议我在 .map
中指定 -> CGFloat
,我添加了它,但后来它告诉我应该将 CGFloat
更改为 _
,我最终会遇到更多错误。
这甚至是我应该能够用 Combine 做的事情吗?我会以错误的方式处理这件事吗?我如何正确地重新使用两个不同发布者的 .map
和 .assign
链?
总的来说,我对 Combine 和响应式编程有些陌生,所以如果您有其他建议来改进我在这里做的所有事情,请告诉我。
处理此问题的一种方法是将您的计时器发布者输出映射到主题的值,并将您的通知发布者输出映射到主题的值。然后,您可以将主题、计时器和通知合并到一个发布者中,该发布者在主题的值更改时、计时器触发时以及通知发布时发布主题的值。
import Combine
import Foundation
import UIKit
class DataStore: ObservableObject {
init() {
fractionFilled = CGFloat(fillSubject.value / capacity)
let fillSubject = self.fillSubject // avoid retain cycles in map closures
let timer = Timer.publish(every: 3, on: .main, in: .common)
.autoconnect()
.map { _ in fillSubject.value }
let foreground = NotificationCenter.default
.publisher(for: UIApplication.willEnterForegroundNotification)
.map { _ in fillSubject.value }
let combo = fillSubject.merge(with: timer, foreground)
combo
.map { [capacity] in CGFloat([=10=] / capacity) }
.sink { [weak self] in self?.fractionFilled = [=10=] }
.store(in: &tickets)
}
let capacity: Double = 500
@Published var fractionFilled: CGFloat
private let fillSubject = CurrentValueSubject<Double, Never>(250)
private var tickets: [AnyCancellable] = []
}