You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Combine加载缓存内容?优化代码消除警告

问题:Combine缓存加载中的错误处理警告解决办法

我正在深入学习Combine的用法,想要实现一个从缓存加载内容的逻辑,核心是完成伪代码里的步骤2b:

  • 步骤1:检查缓存中是否有内容
  • 步骤2a:如果没有缓存,就从API客户端获取数据、缓存后返回对应的发布者
  • 步骤2b:如果有缓存,直接返回缓存内容的发布者

现在我的代码能运行,但loadTrainStationsFromCache()里用Just(stations).mapError { _ -> Error in fatalError() }会触发警告:⚠️ Will never be executed on fatalError() since Just can never produce an error,这只是我为了通过编译凑出来的办法,想知道Combine里有什么正确的方式来解决这个问题?

现有代码如下:

public func trainStationsPublisher() -> AnyPublisher<[TrainStation], Error> {
    // Create cache publisher
    let cachePublisher = loadTrainStationsFromCache()
    // Create publisher to fetch train stations from API client (map to new object) and update cache.
    let apiPublisher = apiClient.trainStationsPublisher()
        .map { $0.compactMap(TrainStation.init) }
        .handleEvents(receiveOutput: { (trainStations) in
            self.cachedTrainStations = trainStations
        })
    // Check the cache and return the result if it exists, otherwise fetch data from the api client.
    return cachePublisher
        .append(apiPublisher)
        .first()
        .eraseToAnyPublisher()
}
public func loadTrainStationsFromCache() -> AnyPublisher<[TrainStation], Error> {
    return Deferred<AnyPublisher<[TrainStation], Error>> { () -> AnyPublisher<[TrainStation], Error> in
        if let stations = self.cachedTrainStations {
            // This is the best I've managed to come up with.
            // Just mapping a non-existent error in order to get the desired return type.
            // Note: fatalError() will never ever be executed since Just(stations) will never fail.
            // How am I suppose to return the cached stations in a publisher?
            return Just(stations).mapError { _ -> Error in fatalError() }
                .eraseToAnyPublisher()
        } else {
            return Empty<[TrainStation], Error>().eraseToAnyPublisher()
        }
    }.eraseToAnyPublisher()
}

解决方案

嘿,这个问题我之前也碰到过!本质上是因为Just是一个永远不会失败的发布者,你硬给它加了一个永远不会触发的mapError,Swift编译器当然会提醒你这段代码完全无效。下面给你几个Combine原生的正确解决办法:

方式1:使用Result.Publisher(最贴合你的需求)

Result类型可以直接包装你的成功值,然后转换成发布者,它的失败类型天然支持Error,完美匹配你的返回类型,不需要任何多余的错误映射:

修改loadTrainStationsFromCache()方法:

public func loadTrainStationsFromCache() -> AnyPublisher<[TrainStation], Error> {
    return Deferred {
        if let stations = self.cachedTrainStations {
            // 直接用Result包装成功值,转换成符合要求的发布者
            return Result.Publisher(stations)
                .eraseToAnyPublisher()
        } else {
            return Empty<[TrainStation], Error>().eraseToAnyPublisher()
        }
    }.eraseToAnyPublisher()
}

这样修改后,警告会直接消失,逻辑也更清晰——Result.Publisher(stations)会创建一个只发送缓存数据、然后完成的发布者,完全符合你需要的AnyPublisher<[TrainStation], Error>类型。

方式2:使用Future(适合后续扩展异步缓存操作)

如果之后你需要在缓存加载时做一些异步操作(比如从磁盘读取缓存),Future会是更灵活的选择。在当前场景下也可以用,但需要你自定义一个错误类型:

首先定义缓存相关的错误:

enum CacheError: Error {
    case empty
}

然后修改loadTrainStationsFromCache():

public func loadTrainStationsFromCache() -> AnyPublisher<[TrainStation], Error> {
    return Deferred {
        Future<[TrainStation], Error> { promise in
            if let stations = self.cachedTrainStations {
                promise(.success(stations))
            } else {
                promise(.failure(CacheError.empty))
            }
        }
    }.eraseToAnyPublisher()
}

不过这种方式在你当前的场景里有点冗余,因为你原本是用Empty来表示“没有缓存”的情况,所以第一种方式更简洁。

额外优化:简化主逻辑

其实你还可以直接简化trainStationsPublisher()的逻辑,不需要单独写loadTrainStationsFromCache()方法,也不用append+first()的组合:

public func trainStationsPublisher() -> AnyPublisher<[TrainStation], Error> {
    // 先检查缓存,有就直接返回
    if let stations = cachedTrainStations {
        return Result.Publisher(stations).eraseToAnyPublisher()
    } else {
        // 没有缓存就调用API,缓存后返回
        return apiClient.trainStationsPublisher()
            .map { $0.compactMap(TrainStation.init) }
            .handleEvents(receiveOutput: { self.cachedTrainStations = $0 })
            .eraseToAnyPublisher()
    }
}

这样逻辑更直接,也避免了Deferred的使用(当然如果你的缓存加载是异步操作,Deferred还是需要保留的)。

内容的提问来源于stack exchange,提问作者Groot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:54:32