swift 中的串行网络调用序列
Sequence of serial network calls in swift
有时我需要进行一系列网络调用,其中每个调用都依赖于之前的调用,因此它们必须连续进行。通常,网络调用将完成处理程序作为参数。可以通过嵌套的完成处理程序完成一系列调用,但这很难阅读。
作为替代方案,我一直在将进程分派到全局队列并使用 Grand Central Dispatch DispatchSemaphore
来停止直到网络查询 returns。但它似乎不是很优雅。这是一些示例代码:
let networkSemaphore = DispatchSemaphore(value: 0)
var body: some View {
Text("Hello, world!")
.padding()
.onAppear { doChainOfEvents() }
}
func networkFetch(item: String, callback: @escaping (Int) -> Void) {
print("Loading \(item)...")
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(2)) {
let numLoaded = Int.random(in: 1...100)
callback(numLoaded)
networkSemaphore.signal()
}
networkSemaphore.wait()
}
func doChainOfEvents() {
DispatchQueue(label: "com.test.queue.serial").async {
networkFetch(item: "accounts") { num in
print("\(num) accounts loaded.")
}
networkFetch(item: "history") { num in
print("\(num) messages loaded.")
}
networkFetch(item: "requests") { num in
print("\(num) requests loaded")
}
print("Network requests complete. ✓")
}
print("Control flow continues while network calls operate.")
}
和doChainOfEvents()
的打印结果:
Control flow continues while network calls operate.
Loading accounts... // (2 second delay)
79 accounts loaded.
Loading history... // (2 second delay)
87 messages loaded.
Loading requests... // (2 second delay)
54 requests loaded
Network requests complete. ✓
你能想出更优雅的方法来实现这个吗?在我看来应该有一个使用 Grand Central Dispatch 的人。我可以使用 DispatchGroup
代替信号量,但我认为它不会给我带来任何好处,并且一次只有一个项目的一组看起来很愚蠢。
使用 Combine,您可以链接一系列请求,例如,创建一个 属性 来保存 AnyCancellable
对象:
var cancellable: AnyCancellable?
然后,假设您需要“帐户”来获取“历史记录”,并且需要“历史记录”来获取“请求”,您可以这样做:
func startRequests() {
cancellable = accountsPublisher()
.flatMap { accounts in self.historyPublisher(for: accounts) }
.flatMap { history in self.requestsPublisher(for: history) }
.subscribe(on: DispatchQueue.main)
.sink { completion in
print(completion)
} receiveValue: { requests in
print(requests)
}
}
这将 运行 发布商按顺序将值从一个传递到下一个。
这与手头的问题无关,但这是我的模型:
struct Account: Codable {
let accountNumber: String
}
struct History: Codable {
let history: String
}
struct Request: Codable {
let identifier: String
}
struct ResponseObject<T: Decodable>: Decodable {
let json: T
let origin: String
}
func accountsPublisher() -> AnyPublisher<[Account], Error> {
URLSession.shared
.dataTaskPublisher(for: accountRequest)
.map(\.data)
.decode(type: ResponseObject<[Account]>.self, decoder: decoder)
.map(\.json)
.eraseToAnyPublisher()
}
func historyPublisher(for accounts: [Account]) -> AnyPublisher<History, Error> {
URLSession.shared
.dataTaskPublisher(for: historyRequest)
.map(\.data)
.decode(type: ResponseObject<History>.self, decoder: decoder)
.map(\.json)
.eraseToAnyPublisher()
}
func requestsPublisher(for history: History) -> AnyPublisher<[Request], Error> {
URLSession.shared
.dataTaskPublisher(for: requestsRequest)
.map(\.data)
.decode(type: ResponseObject<[Request]>.self, decoder: decoder)
.map(\.json)
.eraseToAnyPublisher()
}
var accountRequest: URLRequest = {
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode([Account(accountNumber: "123"), Account(accountNumber: "789")])
return request
}()
var requestsRequest: URLRequest = {
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode([Request(identifier: "bar"), Request(identifier: "baz")])
return request
}()
var historyRequest: URLRequest = {
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode(History(history: "foo"))
return request
}()
现在,在上面的示例中,我使用 httpbin.org/post
在 json
键下向我鹦鹉学舌返回数据。显然,这不是一个现实的场景,但它说明了将各种发布者链接在一起的模式(并使用 Combine 摆脱编写命令式代码的杂草)。
所以不要迷失在杂乱无章的模型中,而是要关注此时间轴中请求的顺序性质:
或查看 示例,了解如何使用 Combine 并发执行请求,但将并发程度限制在合理范围内。只要有可能,运行 并发请求,否则,网络延迟效应会加剧并使整个过程变慢。
有时我需要进行一系列网络调用,其中每个调用都依赖于之前的调用,因此它们必须连续进行。通常,网络调用将完成处理程序作为参数。可以通过嵌套的完成处理程序完成一系列调用,但这很难阅读。
作为替代方案,我一直在将进程分派到全局队列并使用 Grand Central Dispatch DispatchSemaphore
来停止直到网络查询 returns。但它似乎不是很优雅。这是一些示例代码:
let networkSemaphore = DispatchSemaphore(value: 0)
var body: some View {
Text("Hello, world!")
.padding()
.onAppear { doChainOfEvents() }
}
func networkFetch(item: String, callback: @escaping (Int) -> Void) {
print("Loading \(item)...")
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(2)) {
let numLoaded = Int.random(in: 1...100)
callback(numLoaded)
networkSemaphore.signal()
}
networkSemaphore.wait()
}
func doChainOfEvents() {
DispatchQueue(label: "com.test.queue.serial").async {
networkFetch(item: "accounts") { num in
print("\(num) accounts loaded.")
}
networkFetch(item: "history") { num in
print("\(num) messages loaded.")
}
networkFetch(item: "requests") { num in
print("\(num) requests loaded")
}
print("Network requests complete. ✓")
}
print("Control flow continues while network calls operate.")
}
和doChainOfEvents()
的打印结果:
Control flow continues while network calls operate. Loading accounts... // (2 second delay) 79 accounts loaded. Loading history... // (2 second delay) 87 messages loaded. Loading requests... // (2 second delay) 54 requests loaded Network requests complete. ✓
你能想出更优雅的方法来实现这个吗?在我看来应该有一个使用 Grand Central Dispatch 的人。我可以使用 DispatchGroup
代替信号量,但我认为它不会给我带来任何好处,并且一次只有一个项目的一组看起来很愚蠢。
使用 Combine,您可以链接一系列请求,例如,创建一个 属性 来保存 AnyCancellable
对象:
var cancellable: AnyCancellable?
然后,假设您需要“帐户”来获取“历史记录”,并且需要“历史记录”来获取“请求”,您可以这样做:
func startRequests() {
cancellable = accountsPublisher()
.flatMap { accounts in self.historyPublisher(for: accounts) }
.flatMap { history in self.requestsPublisher(for: history) }
.subscribe(on: DispatchQueue.main)
.sink { completion in
print(completion)
} receiveValue: { requests in
print(requests)
}
}
这将 运行 发布商按顺序将值从一个传递到下一个。
这与手头的问题无关,但这是我的模型:
struct Account: Codable {
let accountNumber: String
}
struct History: Codable {
let history: String
}
struct Request: Codable {
let identifier: String
}
struct ResponseObject<T: Decodable>: Decodable {
let json: T
let origin: String
}
func accountsPublisher() -> AnyPublisher<[Account], Error> {
URLSession.shared
.dataTaskPublisher(for: accountRequest)
.map(\.data)
.decode(type: ResponseObject<[Account]>.self, decoder: decoder)
.map(\.json)
.eraseToAnyPublisher()
}
func historyPublisher(for accounts: [Account]) -> AnyPublisher<History, Error> {
URLSession.shared
.dataTaskPublisher(for: historyRequest)
.map(\.data)
.decode(type: ResponseObject<History>.self, decoder: decoder)
.map(\.json)
.eraseToAnyPublisher()
}
func requestsPublisher(for history: History) -> AnyPublisher<[Request], Error> {
URLSession.shared
.dataTaskPublisher(for: requestsRequest)
.map(\.data)
.decode(type: ResponseObject<[Request]>.self, decoder: decoder)
.map(\.json)
.eraseToAnyPublisher()
}
var accountRequest: URLRequest = {
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode([Account(accountNumber: "123"), Account(accountNumber: "789")])
return request
}()
var requestsRequest: URLRequest = {
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode([Request(identifier: "bar"), Request(identifier: "baz")])
return request
}()
var historyRequest: URLRequest = {
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode(History(history: "foo"))
return request
}()
现在,在上面的示例中,我使用 httpbin.org/post
在 json
键下向我鹦鹉学舌返回数据。显然,这不是一个现实的场景,但它说明了将各种发布者链接在一起的模式(并使用 Combine 摆脱编写命令式代码的杂草)。
所以不要迷失在杂乱无章的模型中,而是要关注此时间轴中请求的顺序性质:
或查看