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

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash

Redis中Hash从使用过程到原理说明

《Redis中Hash从使用过程到原理说明》RedisHash结构用于存储字段-值对,适合对象数据,支持HSET、HGET等命令,采用ziplist或hashtable编码,通过渐进式rehash优化... 目录一、开篇:Hash就像超市的货架二、Hash的基本使用1. 常用命令示例2. Java操作示例三

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他