第9章:网络¶
作为程序员,我们所做的许多工作都围绕着网络。与后端通信,获取数据,推送更新,编码和解码JSON
......这是移动开发人员的日常。
Combine
提供了一些精选的API
,以帮助声明性地执行常见任务。这些API
围绕着现代应用程序的两个关键组件展开:
- 使用
URLSession
执行网络请求。 - 使用
Codable
协议对JSON
数据进行编码和解码。
URLSession
扩展¶
URLSession
是执行网络数据传输任务的标准方式。它提供了一个现代化的异步API
,具有强大的配置选项和完全透明的背景支持。它支持各种操作,例如:
- 数据传输任务,以检索
URL
的内容。 - 下载任务以检索
URL
的内容并将其保存到文件中。 - 上传任务以将文件和数据上传到
URL
。 - 流化任务,在双方之间流化数据。
- 连接到网络套字节程序的
Websocket
任务。
其中,只有第一个,数据传输任务,暴露了一个Combine
发布者。Combine
使用单个API
处理这些任务,具有两个变体,获取URLRequest
或仅取URL
。
以下是如何使用此API
的了解:
guard let url = URL(string: "https://mysite.com/mydata.json") else {
return
}
// 1
let subscription = URLSession.shared
// 2
.dataTaskPublisher(for: url)
.sink(receiveCompletion: { completion in
// 3
if case .failure(let err) = completion {
print("Retrieving data failed with error \(err)")
}
}, receiveValue: { data, response in
// 4
print("Retrieved data of size \(data.count), response = \(response)")
})
以下是此代码的情况:
- 保留生成的订阅至关重要;否则,它将立即取消,请求永远不会执行。
- 您正在使用
dataTaskPublisher(for:)
的重载,该重载将URL
作为参数。 - 确保您始终处理错误!网络连接容易出现故障。
- 结果是一个同时带有
Data
对象和URLResponse
元组。
如您所见,Combine
在URLSession.dataTask
上提供了一个透明的裸骨发布者抽象,只暴露了发布者而不是闭包。
可提供可提供的支持¶
Codable
协议是一种现代、功能强大且仅限Swift
的编码和解码机制,您绝对应该了解。如果您不这样做,请帮自己一个忙,并从raywenderlich.com上的苹果文档和教程中了解它!
Foundation
支持通过JSONEncoder
和JSONDecoder
对JSON
进行编码和解码。您还可以使用PropertyListEncoder
和PropertyListDecoder
,但这些在网络请求中不太有用。
在前面的示例中,您下载了一些JSON
。 当然,您可以使用JSONDecoder
对其进行解码:
let subscription = URLSession.shared
.dataTaskPublisher(for: url)
.tryMap { data, _ in
try JSONDecoder().decode(MyType.self, from: data)
}
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("Retrieving data failed with error \(err)")
}
}, receiveValue: { object in
print("Retrieved object \(object)")
})
您可以在tryMap
中解码JSON
,该尝试映射有效,但Combine
提供了一个操作符来帮助减少样板:decode(type:decoder:)
在上面的示例中,将tryMap
操作符替换为以下行:
.map(\.data)
.decode(type: MyType.self, decoder: JSONDecoder())
不幸的是,由于dataTaskPublisher(for:)
会发出元组,如果不首先使用仅发送结果Data
部分的map(_:)
,您就无法直接使用decode(type:decoder:)
。
唯一的优势是,在设置发布者时,您只实例化一次JSONDecoder
,而不是每次在tryMap(_:)
闭包中创建它。
将网络数据发布给多个订阅者¶
每次您订阅发布者时,它都会开始工作。在网络请求的情况下,这意味着如果多个订阅者需要结果,则多次发送相同的请求。
令人惊讶的是,像其他框架一样,Combine
缺乏操作符来简化这一点。您可以使用share()
操作符,但这很棘手,因为您需要在结果回来之前订阅所有订阅者。
除了使用缓存机制外,一个解决方案是使用multicast()
操作符,该操作符创建一个ConnectablePublisher
,通过Subject
发布值。它允许您多次订阅主题,然后在您准备好后调用发布者的connect()
方法:
let url = URL(string: "https://www.raywenderlich.com")!
let publisher = URLSession.shared
// 1
.dataTaskPublisher(for: url)
.map(\.data)
.multicast { PassthroughSubject<Data, URLError>() }
// 2
let subscription1 = publisher
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("Sink1 Retrieving data failed with error \(err)")
}
}, receiveValue: { object in
print("Sink1 Retrieved object \(object)")
})
// 3
let subscription2 = publisher
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("Sink2 Retrieving data failed with error \(err)")
}
}, receiveValue: { object in
print("Sink2 Retrieved object \(object)")
})
// 4
let subscription = publisher.connect()
在这个代码中,您:
- 创建您的
DataTaskPublisher
,map
到其数据,然后进行multicast
。您通过的闭包必须返回适当类型的主题。或者,您可以将现有主题传递给multicast(subject:)
您将在第13章“资源管理”中了解有关multicast
的更多信息。 - 首次订阅发布者。由于它是
ConnectablePublisher
,它不会立即开始工作。 - 第二次订阅。
- 准备好后,连接发布者。它将开始工作,并向所有订阅者推送价值。
使用此代码,您将发送一次请求,并与两个订阅者共享结果。
Note
确保存储所有Cancellable
;否则,当离开当前代码范围时,它们将被释放和取消,在这种情况下,这将是即时的。
这个过程仍然有点复杂,因为Combine
不像其他被动框架那样为此类场景提供操作符。在第18章“自定义发布者和处理背压”中,您将探索制定更好的解决方案。
关键点¶
Combine
为其dataTask(with:completionHandler:)
方法提供了一个基于发布者的抽象,称为dataTaskPublisher(for:)
- 您可以使用发送
Data
值的发布者上的内置decode
操作符解码符合Codable
的模型。 - 虽然没有操作符可以与多个订阅者共享订阅的重播,但您可以使用
ConnectablePublisher
和multicast
操作符重新创建此行为。
接下来去哪?¶
完成这一章做得很好!
如果您想了解有关使用Codable
的更多信息,您可以查看以下资源:
raywenderlich.com
上的“在Swift
中编码和解码”:https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swiftApple
官方文档中的“编码和解码自定义类型”:https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types