Combine - 如果在线获取失败,如何继续解码本地 json 文件?

Combine - how to proceed to decode a local json file if online fetch failed?

我的 Xcode 工作区中有一个在线托管的最新 json 文件和一个本地 json 文件。如果获取失败,我想继续解码本地存储的文件:MyError.fetchError 例如没有互联网连接。这是管道:

func fetchAndDecode<T: Decodable>(url: URL) -> AnyPublisher<T, MyError> {
    fetchURL(url: url)
        .decode(type: T.self, decoder: JSONDecoder())
        .mapError { error in
            if let error = error as? DecodingError {
                return MyError.parsingError
            }  else {
                return MyError.fetchError //here somehow proceed to parse local json file
            }
        }
        .eraseToAnyPublisher()
}

如何实现?

.mapError 是错误的运算符,因为它只考虑 Error 分支。

fetchURL returns 显然Data,所以在解码数据之前你必须用本地数据替换获取错误。

.decode... 行之前插入

.replaceError(with: try! Data(contentsOf: Bundle.main.url(forResource: "local", withExtension: "json")!))

并删除 .mapError 运算符。

local.json表示bundle中本地文件的文件名。

我可以使用为 iOS 15.

引入的 async 函数提出替代但类似的方法来下载数据和处理错误

创建一个异步读取数据的函数,如果连接有效,return从服务器读取数据,否则如果发现问题,它将 return 本地 JSON :

func getData(fromURL url: URL) async -> Data {

        let request = URLRequest(url: url)
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
                  print("HTTP response: \(response.debugDescription)")

              // Found an issue: return the local JSON
              return localJSON
              }

        // If everything is OK, return the data from the server
        return data
}

解码数据returned:

// Use the code below in an asynchronous environment -
// either an async function or inside a Task { } closure

        let data = await getData(fromURL: url)
        do {
            let decoded = try JSONDecoder().decode(T.self, from: data)
            print("Decoded JSON: \(decoded)")
            return decoded
        } catch {
            print("Error decoding JSON: \(error), \(error.localizedDescription)")
        }