Combine 系列
- Swift Combine 从入门到精通一
- Swift Combine 发布者订阅者操作者 从入门到精通二
- Swift Combine 管道 从入门到精通三
- Swift Combine 发布者publisher的生命周期 从入门到精通四
- Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
- Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
- Swift 使用 Combine 进行开发 从入门到精通七
- Swift 使用 Combine 管道和线程进行开发 从入门到精通八
- Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
- Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
- Swift Combine 用 Future 来封装异步请求 从入门到精通十一
- Swift Combine 有序的异步操作 从入门到精通十二
1. 错误处理
上述示例都假设,如果发生错误情况,订阅者将处理这些情况。 但是,你并不总是能够控制订阅者的要求——如果你使用 SwiftUI,情况可能如此。 在这些情况下,你需要构建管道,以便输出类型与订阅者的类型匹配。 这意味着你在处理管道内的任何错误。
例如,如果你正在使用 SwiftUI,并且你希望使用 assign
在按钮上设置 isEnabled
属性,则订阅者将有几个要求:
- 订阅者应匹配
<Bool, Never>
的类型输出 - 应该在主线程调用订阅者
如果发布者抛出一个错误(例如 URLSession.dataTaskPublisher
),你需要构建一个管道来转换输出类型,还需要处理管道内的错误,以匹配错误类型 <Never>
。
如何处理管道内的错误取决于管道的定义方式。 如果管道设置为返回单个结果并终止, 一个很好的例子就是 使用 catch
处理一次性管道中的错误。 如果管道被设置为持续更新,则错误处理要复杂一点。 这种情况下的一个很好的例子是 使用 flatMap 和 catch 在不取消管道的情况下处理错误。
2.使用 assertNoFailure 验证未发生失败
目的:验证管道内未发生错误
在管道中测试常量时,断言 assertNoFailure
非常有用,可将失败类型转换为 <Never>
。 如果断言被触发,该操作符将导致应用程序终止(或测试时导致调试器崩溃)。
这对于验证已经处理过错误的常量很有用。 比如你确信你处理了错误,对管道进行了 map
操作,该操作可以将 <Error>
的失败类型转换为 <Never>
传给所需的订阅者。
更有可能的是,你希望将错误处理掉,而不是终止应用程序。 期待后面的 使用 catch 处理一次性管道中的错误 和 使用 flatMap 和 catch 在不取消管道的情况下处理错误 模式吧,它们会告诉你如何提供逻辑来处理管道中的错误。
3. 使用 catch 处理一次性管道中的错误
目的:如果你需要在管道内处理失败,例如在使用 assign
操作符或其他要求失败类型为 <Never>
的操作符之前,你可以使用 catch
来提供适当的逻辑。
catch
处理错误的方式,是将上游发布者替换为另一个发布者,这是你在闭包中用返回值提供的。
请注意,这实际上终止了管道。 如果你使用的是一次性发布者(不创建多个事件),那这就没什么。
例如,URLSession.dataTaskPublisher 是一个一次性的发布者,你可以使用 catch 在发生错误时返回默认值,以确保你得到响应结果。 扩展我们以前的示例以提供默认的响应:
struct IPInfo: Codable {
// matching the data structure returned from ip.jsontest.com
var ip: String
}
let myURL = URL(string: "http://ip.jsontest.com")
// NOTE(heckj): you'll need to enable insecure downloads in your Info.plist for this example
// since the URL scheme is 'http'
let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!)
// the dataTaskPublisher output combination is (data: Data, response: URLResponse)
.map({ (inputTuple) -> Data in
return inputTuple.data
})
.decode(type: IPInfo.self, decoder: JSONDecoder())
.catch { err in
return Publishers.Just(IPInfo(ip: "8.8.8.8"))
}
.eraseToAnyPublisher()
- 通常,
catch
操作符将被放置在几个可能失败的操作符之后,以便在之前任何可能的操作失败时提供回退或默认值。 - 使用
catch
时,你可以得到错误类型,并可以检查它以选择如何提供响应。 Just
发布者经常用于启动另一个一次性管道,或在发生失败时直接提供默认的响应。
此技术的一个可能问题是,如果你希望原始发布者生成多个响应值,但使用 catch 之后原始管道就已结束了。 如果你正在创建一条对 @Published
属性做出响应的管道,那么在任何失败值激活 catch
操作符之后,管道将不再做出进一步响应。 有关此工作原理的详细信息,请参阅 catch
。
如果你要继续响应错误并处理它们,请参阅 使用 flatMap 和 catch 在不取消管道的情况下处理错误。
4. 在发生暂时失败时重试
目的: 当 .failure
发生时,retry
操作符可以被包含在管道中以重试订阅。
当向 dataTaskPublisher
请求数据时,请求可能会失败。 在这种情况下,你将收到一个带有 error
的 .failure
事件。 当失败时,retry
操作符将允许你对相同请求进行一定次数的重试。 当发布者不发送 .failure
事件时,retry
操作符会传递结果值。 retry
仅在发送 .failure
事件时才在 Combine
管道内做出响应。
当 retry
收到 .failure
结束事件时,它重试的方式是给它所链接的操作符或发布者重新创建订阅。
当尝试请求连接不稳定的网络资源时,通常需要 retry
操作符,或者再次请求时可能会成功的情况。 如果指定的重试次数全部失败,则将 .failure
结束事件传递给订阅者。
在下面的示例中,我们将 retry
与 delay
操作符相结合使用。 我们使用延迟操作符在下一个请求之前使其出现少量随机延迟。 这使得重试的尝试行为被分隔开,使重试不会快速连续的发生。
此示例还包括使用 tryMap
操作符以更全面地检查从 dataTaskPublisher
返回的任何 URL 响应。 服务器的任何响应都由 URLSession
封装,并作为有效的响应转发。 URLSession
不将 404 Not Found
的 http
响应视为错误响应,也不将任何 50x 错误代码视作错误。 使用 tryMap
,我们可检查已发送的响应代码,并验证它是 200 的成功响应代码。 在此示例中,如果响应代码不是 200 ,则会抛出一个异常 —— 这反过来又会导致 tryMap
操作符传递 .failure
事件,而不是数据。 此示例将 tryMap
设置在 retry
操作符 之后,以便仅在网站未响应时重新尝试请求。
let remoteDataPublisher = urlSession.dataTaskPublisher(for: self.URL!)
.delay(for: DispatchQueue.SchedulerTimeType.Stride(integerLiteral: Int.random(in: 1..<5)), scheduler: backgroundQueue)
.retry(3)
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw TestFailureCondition.invalidServerResponse
}
return data
}
.decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder())
.subscribe(on: backgroundQueue)
.eraseToAnyPublisher()
delay
操作符将流经过管道的结果保持一小段时间,在这个例子中随机选择1至5秒。通过在管道中添加延迟,即使原始请求成功,重试也始终会发生。- 重试被指定为尝试3次。 如果每次尝试都失败,这将导致总共 4 次尝试 - 原始请求和 3 次额外尝试。
tryMap
被用于检查dataTaskPublisher
返回的数据,如果服务器的响应数据有效,但不是 200 HTTP 响应码,则返回.failure
完成事件。
使用
retry
操作符与URLSession.dataTaskPublisher
时,请验证你请求的 URL 如果反复请求或重试,不会产生副作用。 理想情况下,此类请求应具有幂等性。 如果没有,retry 操作符可能会发出多个请求,并产生非常意想不到的副作用。
参考
https://heckj.github.io/swiftui-notes/index_zh-CN.html
代码
https://github.com/heckj/swiftui-notes