ios - Swift Combine:如何创建可重用的 Publishers.Map 以连接到多个上游 Publisher?
问题描述
我正试图将我的头脑围绕在 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{ $0 }?
.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
它建议我指定-> CGFloat
in .map
,我添加了它,但随后它告诉我应该更改CGFloat
为_
并最终出现更多错误。
这甚至是我应该能够用Combine做的事情吗?我是不是走错了路?如何正确地与两个不同的发布者重复使用.map
and.assign
链?
一般来说,我对组合和反应式编程有点陌生,所以如果您有其他建议来改进我在这里做所有事情的方式,请告诉我。
解决方案
处理此问题的一种方法是将您的计时器发布者输出映射到主题的值,并将您的通知发布者输出映射到主题的值。然后,您可以将主题、计时器和通知合并到一个发布者中,该发布者在主题值更改、计时器触发以及发布通知时发布主题值。
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($0 / capacity) }
.sink { [weak self] in self?.fractionFilled = $0 }
.store(in: &tickets)
}
let capacity: Double = 500
@Published var fractionFilled: CGFloat
private let fillSubject = CurrentValueSubject<Double, Never>(250)
private var tickets: [AnyCancellable] = []
}
推荐阅读
- xpath - JSNAPY:有没有办法测试 xpath 节点属性
- javascript - 使用 div 中的按钮关闭带有母版页的 ASP.NET 页面上的 DIV,其中 JQuery 用于显示 div
- ios - 如何根据 iPhone 向上移动文本字段。尺寸迅速
- javascript - Swiper Slider - 视频结束时继续下一张幻灯片
- javascript - 如何在反应中加载自定义prismjs语法文件?
- regex - Hive regexp_replace
- c# - Xamarin.Forms - 如何制作点击计数器?
- aws-amplify - AWS Amplify 构建过程中的缓存工件是什么?
- c++ - 在函数 'int main(int, char**) 中,未声明 'MIN'
- java - spring boot API - 文档处理并在文档上并行执行python脚本