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中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud