如何用Combine结合Timer检测GPS数据是否过期
问题描述
正在将部分代码迁移至Combine框架以熟悉其使用,基础部分进展顺利,但在检测GPS数据准确性及是否过期的需求上遇到了难点。
现有代码如下:
let locationPublisher = PassthroughSubject<CLLocation,Never>() private var cancellableSet: Set<AnyCancellable> = [] var status:GPSStatus = .red //enum
在初始化方法中:
locationPublisher .map(gpsStatus(from:)) //maps CLLocation to GPSStatus enum .assign(to: \.gpsStatus, on: self) .store(in: &cancellableSet) locationPublisher.sink() { [weak self] location in self?.statusTimer?.invalidate() self?.setStatusTimer() } .store(in: &cancellableSet) setStatusTimer()
setStatusTimer函数实现:
func setStatusTimer () { statusTimer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) {@MainActor _ in self.updateGPSStatus(.red) } }
想知道是否有更符合Combine风格的实现方式?了解到Timer.TimerPublisher,但不清楚如何整合。倾向于使用类似combineLatest的方式,一个输入是GPS状态发布者,另一个是当上游发布者超过指定时间未发送事件时触发的发布者。
符合Combine风格的实现方案
可以利用Combine的响应式API完全替代手动管理Timer的逻辑,以下两种方案都能满足需求:
方案一:使用timeout操作符(简洁直接)
timeout操作符会在指定时间内未收到上游事件时触发自定义逻辑,刚好适配“超时后切换为red状态”的需求:
locationPublisher .map(gpsStatus(from:)) // 20秒未收到新位置时,触发自定义错误返回.red .timeout(.seconds(20), scheduler: DispatchQueue.main, customError: { .red }) // 将错误转换为正常输出,匹配原发布者的Never错误类型 .catch { Just($0) } .assign(to: \.status, on: self) .store(in: &cancellableSet) // 初始化状态保持原逻辑 status = .red
方案二:结合Timer.TimerPublisher与switchLatest(灵活可控)
如果需要更精细的流程控制,比如每次收到位置后自动重启计时器,可以用switchLatest切换计时器发布者,旧计时器会自动被取消:
locationPublisher .map { location in let currentStatus = gpsStatus(from: location) // 合并两个发布者:当前GPS状态 + 20秒后触发的red状态 return Just(currentStatus) .merge(with: Timer.publish(every: 20, on: .main, in: .common) .autoconnect() .map { _ in GPSStatus.red } .prefix(1)) // 仅取第一次超时事件 } .switchLatest() // 收到新位置时自动切换到新的合并流,旧计时器失效 .assign(to: \.status, on: self) .store(in: &cancellableSet) // 初始化状态保持原逻辑 status = .red
核心优势
- 完全抛弃命令式的Timer管理逻辑,避免手动调用
invalidate - 状态更新完全由事件流驱动,逻辑更清晰
- 代码更简洁,贴合Combine响应式设计理念
内容的提问来源于stack exchange,提问作者Daniel K
相关产品推荐
相关产品推荐

