在 swift 中按顺序同步多个 Web 服务调用
Synchronise multiple web service calls in serial order in swift
我正在访问 Web 服务 url 10 次并得到响应。我正在使用 Alamofire
和 SwiftyJSON
。这是我的控制器代码
class ViewController: UIViewController {
let dispatchGroup = DispatchGroup()
var weatherServiceURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
weatherService()
dispatchGroup.notify(queue: .main) {
print("All services complete")
}
}
func weatherService() {
for i in 1...10 {
dispatchGroup.enter()
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
return
}
guard let response = response else { return }
print("\n\(response) \n\(count) response\n")
self.dispatchGroup.leave()
}
}
}
}
这是我的服务处理器class代码
class APIManager: NSObject {
class func apiGet(serviceName:String,parameters: [String:Any]?, completionHandler: @escaping (JSON?, NSError?, Int) -> ()) {
Alamofire.request(serviceName, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
if let data = response.result.value{
let json = JSON(data)
completionHandler(json,nil, parameters!["counter"] as! Int)
}
break
case .failure(_):
completionHandler(nil,response.result.error as NSError?, parameters!["counter"] as! Int)
break
}
}
}
}
我正在发送一个带有 for 循环索引的计数器键,只是为了跟踪返回哪个索引的响应。但是响应不是按顺序出现的。我们可以在第二和第一响应之前期待第三响应。这是因为带有 APIManager.apiGet
函数调用的 API 调用是异步的并且正在转义,因此继续 for 循环。
我还使用了 dispatchQueue
let dispatchQueue = DispatchQueue(label: "com.test.Queue", qos: .userInteractive)
并将函数转换为:
func weatherService() {
for i in 1...10 {
dispatchGroup.enter()
dispatchQueue.async {
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
return
}
guard let response = response else { return }
print("\n\(response) \n\(count) response\n")
self.dispatchGroup.leave()
}
}
}
}
结果相同,因为服务调用代码是异步的。如果我们
dispatchQueue.sync {
//service call
}
然后我们也不会按顺序获得响应,因为异步和 dispatchQueue 中的网络调用假定任务已完成。
条件是在不冻结 UI 的情况下仅以异步方式访问服务。如果我以同步方式访问服务,那么我会得到我想要的结果。但是阻塞主线程是完全不能接受的。
我可以使用数组或一些全局布尔变量来管理这个东西,但我不想使用它们。有没有其他方法可以按调用它的顺序获得响应?感谢任何帮助或提示。
按顺序进行 api 调用的最简单方法是在前一个完成处理程序中执行 "next" 调用,而不是在 for
外部使用循环api 次调用。
func weatherService(counter: Int = 1, maxCount: Int = 10) {
guard counter <= maxCount else {
return
}
dispatchGroup.enter()
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
self.weatherService(counter: counter+1, maxCount: maxCount)
if let error = error {
print(error.localizedDescription)
self.dispatchGroup.leave()
return
}
guard let response = response else {
self.dispatchGroup.leave()
return
}
print("\n\(response) \n\(count) response\n")
self.dispatchGroup.leave()
}
}
}
我建议不要这样做,除非对顺序有一定的依赖性(即调用 2 需要调用 1 的结果中的信息),因为它比并行请求需要更长的时间。
处理结果可能 return 乱序的事实会好得多。
此外,在使用调度组时,您需要确保在代码完成的所有情况下都调用 dispatchGroup.leave
;在您的情况下,如果发生错误,您不会这样做。如果在一个或多个请求中发生错误,这将导致 dispatchGroup.notify
永远不会触发。
想法
- index1 - 创建闭包时循环中的索引
- index2 - 容器中执行操作的索引
您需要创建带闭包的容器。这个容器将保存所有的闭包。容器将检查 index1 == index2
运行 index1 之前和 if index1 + 1 > exist
.
之后的所有操作
因此,此容器将按升序逐一检查接收到的闭包和 运行 个闭包的顺序。
详情
Xcode9.4.1,Swift4.1
容器
class ActionsRunController {
typealias Func = ()->()
private var actions: [Int: Func] = [:]
private var dispatchSemaphore = DispatchSemaphore(value: 1)
private var firstIndex = 0
private var lastIndex = 0
func add(at index: Int, action: Func?) {
dispatchSemaphore.wait()
actions[index] = action
if lastIndex == index {
while (actions[firstIndex] != nil) {
actions[firstIndex]?()
actions[firstIndex] = nil
firstIndex += 1
}
lastIndex = firstIndex
}
dispatchSemaphore.signal()
}
}
完整代码
Do no forget to add code of the Container here
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
let dispatchGroup = DispatchGroup()
var weatherServiceURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
weatherService()
dispatchGroup.notify(queue: .main) {
print("All services complete")
}
}
func weatherService() {
for i in 0...9 {
dispatchGroup.enter()
APIManager.apiGet(serviceName: self.weatherServiceURL, counter: i) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
return
}
//guard let response = response else { return }
print("[executed] action \(count)")
self.dispatchGroup.leave()
}
}
}
}
class APIManager: NSObject {
private static let actionsRunController = ActionsRunController()
class func apiGet(serviceName:String, counter: Int, completionHandler: @escaping (JSON?, NSError?, Int) -> ()) {
Alamofire.request(serviceName, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
//print("[created] action \(counter)")
switch(response.result) {
case .success(_):
if let data = response.result.value{
let json = JSON(data)
actionsRunController.add(at: counter) {
completionHandler(json, nil, counter)
}
}
break
case .failure(_):
actionsRunController.add(at: counter) {
completionHandler(nil,response.result.error as NSError?, counter)
}
break
}
}
}
}
结果
解决方案:使用DispatchSemaphore
s和一个DispatchQueue
我没有保存闭包,而是决定将所有内容包装在一个调度队列中并在其中使用信号量
//Create a dispatch queue
let dispatchQueue = DispatchQueue(label: "myQueue", qos: .background)
//Create a semaphore
let semaphore = DispatchSemaphore(value: 0)
func weatherService() {
dispatchQueue.async {
for i in 1...10 {
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
self.semaphore.signal()
return
}
guard let response = response else {
self.semaphore.signal()
return
}
print("\(count) ")
//Check by index, the last service in this case
if i == 10 {
print("Services Completed")
} else {
print("An error occurred")
}
// Signals that the 'current' API request has completed
self.semaphore.signal()
}
// Wait until the previous API request completes
self.semaphore.wait()
}
}
print("Start Fetching")
}
输出总是这样
我正在访问 Web 服务 url 10 次并得到响应。我正在使用 Alamofire
和 SwiftyJSON
。这是我的控制器代码
class ViewController: UIViewController {
let dispatchGroup = DispatchGroup()
var weatherServiceURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
weatherService()
dispatchGroup.notify(queue: .main) {
print("All services complete")
}
}
func weatherService() {
for i in 1...10 {
dispatchGroup.enter()
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
return
}
guard let response = response else { return }
print("\n\(response) \n\(count) response\n")
self.dispatchGroup.leave()
}
}
}
}
这是我的服务处理器class代码
class APIManager: NSObject {
class func apiGet(serviceName:String,parameters: [String:Any]?, completionHandler: @escaping (JSON?, NSError?, Int) -> ()) {
Alamofire.request(serviceName, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
if let data = response.result.value{
let json = JSON(data)
completionHandler(json,nil, parameters!["counter"] as! Int)
}
break
case .failure(_):
completionHandler(nil,response.result.error as NSError?, parameters!["counter"] as! Int)
break
}
}
}
}
我正在发送一个带有 for 循环索引的计数器键,只是为了跟踪返回哪个索引的响应。但是响应不是按顺序出现的。我们可以在第二和第一响应之前期待第三响应。这是因为带有 APIManager.apiGet
函数调用的 API 调用是异步的并且正在转义,因此继续 for 循环。
我还使用了 dispatchQueue
let dispatchQueue = DispatchQueue(label: "com.test.Queue", qos: .userInteractive)
并将函数转换为:
func weatherService() {
for i in 1...10 {
dispatchGroup.enter()
dispatchQueue.async {
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
return
}
guard let response = response else { return }
print("\n\(response) \n\(count) response\n")
self.dispatchGroup.leave()
}
}
}
}
结果相同,因为服务调用代码是异步的。如果我们
dispatchQueue.sync {
//service call
}
然后我们也不会按顺序获得响应,因为异步和 dispatchQueue 中的网络调用假定任务已完成。
条件是在不冻结 UI 的情况下仅以异步方式访问服务。如果我以同步方式访问服务,那么我会得到我想要的结果。但是阻塞主线程是完全不能接受的。
我可以使用数组或一些全局布尔变量来管理这个东西,但我不想使用它们。有没有其他方法可以按调用它的顺序获得响应?感谢任何帮助或提示。
按顺序进行 api 调用的最简单方法是在前一个完成处理程序中执行 "next" 调用,而不是在 for
外部使用循环api 次调用。
func weatherService(counter: Int = 1, maxCount: Int = 10) {
guard counter <= maxCount else {
return
}
dispatchGroup.enter()
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
self.weatherService(counter: counter+1, maxCount: maxCount)
if let error = error {
print(error.localizedDescription)
self.dispatchGroup.leave()
return
}
guard let response = response else {
self.dispatchGroup.leave()
return
}
print("\n\(response) \n\(count) response\n")
self.dispatchGroup.leave()
}
}
}
我建议不要这样做,除非对顺序有一定的依赖性(即调用 2 需要调用 1 的结果中的信息),因为它比并行请求需要更长的时间。
处理结果可能 return 乱序的事实会好得多。
此外,在使用调度组时,您需要确保在代码完成的所有情况下都调用 dispatchGroup.leave
;在您的情况下,如果发生错误,您不会这样做。如果在一个或多个请求中发生错误,这将导致 dispatchGroup.notify
永远不会触发。
想法
- index1 - 创建闭包时循环中的索引
- index2 - 容器中执行操作的索引
您需要创建带闭包的容器。这个容器将保存所有的闭包。容器将检查 index1 == index2
运行 index1 之前和 if index1 + 1 > exist
.
因此,此容器将按升序逐一检查接收到的闭包和 运行 个闭包的顺序。
详情
Xcode9.4.1,Swift4.1
容器
class ActionsRunController {
typealias Func = ()->()
private var actions: [Int: Func] = [:]
private var dispatchSemaphore = DispatchSemaphore(value: 1)
private var firstIndex = 0
private var lastIndex = 0
func add(at index: Int, action: Func?) {
dispatchSemaphore.wait()
actions[index] = action
if lastIndex == index {
while (actions[firstIndex] != nil) {
actions[firstIndex]?()
actions[firstIndex] = nil
firstIndex += 1
}
lastIndex = firstIndex
}
dispatchSemaphore.signal()
}
}
完整代码
Do no forget to add code of the Container here
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
let dispatchGroup = DispatchGroup()
var weatherServiceURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
weatherService()
dispatchGroup.notify(queue: .main) {
print("All services complete")
}
}
func weatherService() {
for i in 0...9 {
dispatchGroup.enter()
APIManager.apiGet(serviceName: self.weatherServiceURL, counter: i) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
return
}
//guard let response = response else { return }
print("[executed] action \(count)")
self.dispatchGroup.leave()
}
}
}
}
class APIManager: NSObject {
private static let actionsRunController = ActionsRunController()
class func apiGet(serviceName:String, counter: Int, completionHandler: @escaping (JSON?, NSError?, Int) -> ()) {
Alamofire.request(serviceName, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
//print("[created] action \(counter)")
switch(response.result) {
case .success(_):
if let data = response.result.value{
let json = JSON(data)
actionsRunController.add(at: counter) {
completionHandler(json, nil, counter)
}
}
break
case .failure(_):
actionsRunController.add(at: counter) {
completionHandler(nil,response.result.error as NSError?, counter)
}
break
}
}
}
}
结果
解决方案:使用DispatchSemaphore
s和一个DispatchQueue
我没有保存闭包,而是决定将所有内容包装在一个调度队列中并在其中使用信号量
//Create a dispatch queue
let dispatchQueue = DispatchQueue(label: "myQueue", qos: .background)
//Create a semaphore
let semaphore = DispatchSemaphore(value: 0)
func weatherService() {
dispatchQueue.async {
for i in 1...10 {
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
self.semaphore.signal()
return
}
guard let response = response else {
self.semaphore.signal()
return
}
print("\(count) ")
//Check by index, the last service in this case
if i == 10 {
print("Services Completed")
} else {
print("An error occurred")
}
// Signals that the 'current' API request has completed
self.semaphore.signal()
}
// Wait until the previous API request completes
self.semaphore.wait()
}
}
print("Start Fetching")
}
输出总是这样