Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十

本文主要是介绍Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Combine 系列

  1. Swift Combine 从入门到精通一
  2. Swift Combine 发布者订阅者操作者 从入门到精通二
  3. Swift Combine 管道 从入门到精通三
  4. Swift Combine 发布者publisher的生命周期 从入门到精通四
  5. Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
  6. Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
  7. Swift 使用 Combine 进行开发 从入门到精通七
  8. Swift 使用 Combine 管道和线程进行开发 从入门到精通八
  9. Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
    在这里插入图片描述

1. 使用 dataTaskPublisher 发起网络请求

  • 目的: 一个常见的用例是从 URL 请求 JSON 数据并解码。

这可以通过使用 Combine 的 URLSession.dataTaskPublisher 搭配一系列处理数据的操作符来轻松完成。

最简单的,调用 URLSession 的 dataTaskPublisher,然后在数据到达订阅者之前使用 map 和 decode。

使用此操作的最简单例子可能是:

let myURL = URL(string: "https://postman-echo.com/time/valid?timestamp=2016-10-10")
// checks the validity of a timestamp - this one returns {"valid":true}
// matching the data structure returned from https://postman-echo.com/time/valid
fileprivate struct PostmanEchoTimeStampCheckResponse: Decodable, Hashable { // 1let valid: Bool
}let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!) // 2// the dataTaskPublisher output combination is (data: Data, response: URLResponse).map { $0.data } // 3.decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder()) // 4let cancellableSink = remoteDataPublisher.sink(receiveCompletion: { completion inprint(".sink() received the completion", String(describing: completion))switch completion {case .finished: // 5breakcase .failure(let anError):  // 6print("received error: ", anError)}}, receiveValue: { someValue in // 7print(".sink() received \(someValue)")})
  1. 通常,你将有一个结构体的定义,至少遵循 Decodable 协议(即使没有完全遵循 Codable protocol)。此结构体可以只定义从网络拉取到的 JSON 中你感兴趣的字段。 不需要定义完整的 JSON 结构。
  2. dataTaskPublisher 是从 URLSession 实例化的。 你可以配置你自己的 URLSession,或者使用 shared session.
  3. 返回的数据是一个元组:(data: Data, response: URLResponse)。 map 操作符用来获取数据并丢弃 URLResponse,只把 Data 沿管道向下传递。
  4. decode 用于加载数据并尝试解析它。 如果解码失败,它会抛出一个错误。 如果它成功,通过管道传递的对象将是来自 JSON 数据的结构体。
  5. 如果解码完成且没有错误,则将触发完成操作,并将值传递给 receiveValue 闭包。
  6. 如果发生失败(无论是网络请求还是解码),则错误将被传递到 failure 闭包。
  7. 只有当数据请求并解码成功时,才会调用此闭包,并且收到的数据格式将是结构体 PostmanEchoTimeStampCheckResponse 的实例。

2. 使用 dataTaskPublisher 进行更严格的请求处理

  • 目的: 当 URLSesion 进行连接时,它仅在远程服务器未响应时报告错误。 你可能需要根据状态码将各种响应视为不同的错误。 为此,你可以使用 tryMap 检查 http 响应并在管道中抛出错误。

要对 URL 响应中被认为是失败的操作进行更多控制,可以对 dataTaskPublisher 的元组响应使用 tryMap 操作符。 由于 dataTaskPublisher 将响应数据和 URLResponse 都返回到了管道中,你可以立即检查响应,并在需要时抛出自己的错误。

这方面的一个例子可能看起来像:

let myURL = URL(string: "https://postman-echo.com/time/valid?timestamp=2016-10-10")
// checks the validity of a timestamp - this one returns {"valid":true}
// matching the data structure returned from https://postman-echo.com/time/valid
fileprivate struct PostmanEchoTimeStampCheckResponse: Decodable, Hashable {let valid: Bool
}
enum TestFailureCondition: Error {case invalidServerResponse
}let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!).tryMap { data, response -> Data in  // 1guard let httpResponse = response as? HTTPURLResponse,  // 2 httpResponse.statusCode == 200 else {  // 3throw TestFailureCondition.invalidServerResponse  // 4}return data  // 5}.decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder())let cancellableSink = remoteDataPublisher.sink(receiveCompletion: { completion inprint(".sink() received the completion", String(describing: completion))switch completion {case .finished:breakcase .failure(let anError):print("received error: ", anError)}}, receiveValue: { someValue inprint(".sink() received \(someValue)")})

在 上个模式 中使用了 map 操作符, 这里我们使用 tryMap,这使我们能够根据返回的内容识别并在管道中抛出错误。

  1. tryMap 仍旧获得元组 (data: Data, response: URLResponse),并且在这里定义仅返回管道中的 Data 类型。
  2. tryMap 的闭包内,我们将响应转换为 HTTPURLResponse 并深入进去,包括查看特定的状态码。
  3. 在这个例子中,我们希望将 200 状态码以外的任何响应视为失败。HTTPURLResponse.statusCode 是一种 Int 类型,因此你也可以使用 httpResponse.statusCode > 300 等逻辑。
  4. 如果判断条件未满足,则会抛出我们选择的错误实例:在这个例子中,是 invalidServerResponse
  5. 如果没有出现错误,则我们只需传递 Data 以进行进一步处理。

3. 标准化 dataTaskPublisher 返回的错误

当在管道上触发错误时,不管错误发生在管道中的什么位置,都会发送 .failure 完成回调,并把错误封装在其中。

此模式可以扩展来返回一个发布者,该发布者使用此通用模式可接受并处理任意数量的特定错误。 在许多示例中,我们用默认值替换错误条件。 如果我们想要返回一个发布者的函数,该发布者不会根据失败来选择将发生什么,则同样 tryMap 操作符可以与 mapError 一起使用来转换响应对象以及转换 URLError 错误类型。

enum APIError: Error, LocalizedError {  // 1case unknown, apiError(reason: String), parserError(reason: String), networkError(from: URLError)var errorDescription: String? {switch self {case .unknown:return "Unknown error"case .apiError(let reason), .parserError(let reason):return reasoncase .networkError(let from):  // 2return from.localizedDescription}}
}func fetch(url: URL) -> AnyPublisher<Data, APIError> {let request = URLRequest(url: url)return URLSession.DataTaskPublisher(request: request, session: .shared)  // 3.tryMap { data, response in  // 4guard let httpResponse = response as? HTTPURLResponse else {throw APIError.unknown}if (httpResponse.statusCode == 401) {throw APIError.apiError(reason: "Unauthorized");}if (httpResponse.statusCode == 403) {throw APIError.apiError(reason: "Resource forbidden");}if (httpResponse.statusCode == 404) {throw APIError.apiError(reason: "Resource not found");}if (405..<500 ~= httpResponse.statusCode) {throw APIError.apiError(reason: "client error");}if (500..<600 ~= httpResponse.statusCode) {throw APIError.apiError(reason: "server error");}return data}.mapError { error in  // 5// if it's our kind of error already, we can return it directlyif let error = error as? APIError {return error}// if it is a TestExampleError, convert it into our new error typeif error is TestExampleError {return APIError.parserError(reason: "Our example error")}// if it is a URLError, we can convert it into our more general error kindif let urlerror = error as? URLError {return APIError.networkError(from: urlerror)}// if all else fails, return the unknown error conditionreturn APIError.unknown}.eraseToAnyPublisher()  // 6
}
  1. APIError 是一个错误类型的枚举,我们在此示例中使用该枚举来列举可能发生的所有错误。
  2. .networkError 是 APIError 的一个特定情况,当 URLSession.dataTaskPublisher 返回错误时我们将把错误转换为该类型。
  3. 我们使用标准 dataTaskPublisher 开始生成此发布者。
  4. 然后,我们将路由到 tryMap 操作符来检查响应,根据服务器响应创建特定的错误。
  5. 最后,我们使用 mapError 将任何其他不可忽视的错误类型转换为通用的错误类型 APIError

参考

https://heckj.github.io/swiftui-notes/index_zh-CN.html

代码

https://github.com/heckj/swiftui-notes

这篇关于Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/698901

相关文章

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

MyBatis ParameterHandler的具体使用

《MyBatisParameterHandler的具体使用》本文主要介绍了MyBatisParameterHandler的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录一、概述二、源码1 关键属性2.setParameters3.TypeHandler1.TypeHa

Spring 中的切面与事务结合使用完整示例

《Spring中的切面与事务结合使用完整示例》本文给大家介绍Spring中的切面与事务结合使用完整示例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录 一、前置知识:Spring AOP 与 事务的关系 事务本质上就是一个“切面”二、核心组件三、完