如何在 Swift 中回退缓存时使用 Combine 显示使用网络的弹性搜索结果

How to use Combine to show elastic search results using network while falling back on cache in Swift

我有一个函数,它 returns 使用弹性搜索的项目列表并返回到领域缓存。我想知道如何使用 Combine 来实现相同的目的。

我正在尝试做这样的事情,我为每个商店都有一个发布者,但我无法按分数对它们进行排序。

    func search(for text: String) -> AnyPublisher<[Item], Error> {
          
          return store.search(with: text)
            // Invalid syntax *
            .map { searchResults in
                let sorted = cacheStore.search(with: text)
                    .map { items in
                        items
                            .map { item in (item, searchResults.first { [=11=].id == item.id }?.score ?? 0) }
                            .sorted { [=11=].1 > .1 } // by score
                            .map { [=11=].0 } // to item
                    }
                return sorted.eraseToAnyPublisher()
            }
            // *
            .catch { _ in cacheStore.search(with: text) }
            .eraseToAnyPublisher()
    }

这是原来的函数。

func search(for text: String, completion: @escaping (Result<[Item], Error>) -> Void) {
  
    store.search(with: text) {
        // Search network via elastic search or fall back to cache search
        // searchResults is of type [(id: Int, score: Double)] where id is item.id
        guard let searchResult = [=12=].value, [=12=].isSuccess else {
            return self.cacheStore.search(with: text, completion: completion)
        }
        
        self.cacheStore.fetch(ids: searchResult.map { [=12=].id }) {
            guard let items = [=12=].value, [=12=].isSuccess else {
                return self.cacheStore.search(with: text, completion: completion)
            }
            
            let scoredItems = items
                .map { item in (item, searchResult.first { [=12=].id == item.id }?.score ?? 0) }
                .sorted { [=12=].1 > .1 } // by score
                .map { [=12=].0 } // to item
            
            completion(.success(scoredItems))
        }
    }
}

我认为你的目标是类似于下面的游乐场。

Playground 的大部分代码都是使用 Futures 模拟搜索的代码。特别相关的部分是:

return searchNetwork(key: key)
    .map { key,value in cache[key] = value; return value }
    .catch {_ in searchCache(key: key) }
    .eraseToAnyPublisher()

如果来自 searchNetwork 的网络请求成功,则该值会通过将其添加到缓存的映射,并且 returns 来自网络的值。如果 searchNetwork 失败,则 catch 将替换搜索缓存的发布者。

import Foundation
import Combine

var cache = [
    "one" : "for the money",
    "two" : "for the show"
]

enum SearchError: Error {
    case cacheMiss
    case networkFailure
}

func searchCache(key : String) -> AnyPublisher<String, SearchError>
{
    return Future<String, SearchError> { fulfill in
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
            if let value = cache[key] {
                fulfill(.success(value))
            } else {
                fulfill(.failure(.cacheMiss))
            }
        }
    }.eraseToAnyPublisher()
}

func searchNetwork(key: String) -> AnyPublisher<(String, String), SearchError> {
    return Future<(String, String), SearchError> { fulfill in
        fulfill(.failure(.networkFailure))
    }.eraseToAnyPublisher()
}

func search(for key: String) -> AnyPublisher<String, SearchError> {
    return searchNetwork(key: key)
        .map { key,value in cache[key] = value; return value }
        .catch {_ in searchCache(key: key) }
        .eraseToAnyPublisher()
}

let searchForOne = search(for: "one").sink(
    receiveCompletion:  { debugPrint([=11=]) },
    receiveValue: { print("Search for one : \([=11=])") }
)

let searchForThree = search(for: "three").sink(
    receiveCompletion: { debugPrint([=11=]) },
    receiveValue: { print("Search for three : \([=11=])") }
)

我通过这样做找到了解决方案:

    let cachedPublisher = cacheStore.search(with: text)
    
    let createPublisher: (Item) -> AnyPublisher<Item, Error> = {
        return Just([=10=]).eraseToAnyPublisher()
    }
    
    return store.search(with: request)
        .flatMap { Item -> AnyPublisher<[Item], Error> in
            let ids = searchResults.map { [=10=].id }
            let results = self.cacheStore.fetch(ids: ids, filterActive: true)
                .flatMap { items -> AnyPublisher<[Item], Error> in
                    let sorted = items
                        .map { item in (item, searchResults.first { [=10=].id == item.id }?.score ?? 0) }
                        .sorted { [=10=].1 > .1 } // by score
                        .map{ [=10=].0 } // to item
                    return Publishers.mergeMappedRetainingOrder(sorted, mapTransform: createPublisher) // Helper function that calls Publishers.MergeMany
                }
            return results.eraseToAnyPublisher()
        }
        .catch { _ in cachedPublisher }
        .eraseToAnyPublisher()