Ios 在swift中以串行顺序同步多个web服务调用

Ios 在swift中以串行顺序同步多个web服务调用,ios,swift,grand-central-dispatch,dispatch-async,dispatchworkitem,Ios,Swift,Grand Central Dispatch,Dispatch Async,Dispatchworkitem,我点击了10次web服务url并得到了响应。我正在使用Alamofire和SwiftyJSON。这是我的控制器代码 class ViewController: UIViewController { let dispatchGroup = DispatchGroup() var weatherServiceURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d2

我点击了10次web服务url并得到了响应。我正在使用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 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
            }
        }
    }
}
并将函数转换为:

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 
}
然后,我们也不会以串行顺序获得响应,因为async和dispatchQueue中的网络调用假定任务已完成

条件是仅以异步方式命中服务,而不冻结UI。如果我以同步方式点击服务,那么我会得到我想要的结果。但是阻塞主线程是完全不可接受的


我可以使用数组或一些全局bool变量来管理这件事,但我不想使用它们。是否有其他方法可以按调用顺序获取响应?感谢任何帮助或提示。

获得api调用的最简单方法是在前一个api调用的完成处理程序中执行下一个调用,而不是在api调用之外使用for循环

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号呼叫结果的信息,因为这将比并行请求花费更长的时间

更好的做法是处理结果可能会出现混乱的事实

此外,在使用调度组时,您需要确保在代码完成的所有情况下都调用dispatchGroup.leave;在您的情况下,如果发生错误,您不会这样做。这将导致dispatchGroup.notify never在一个或多个请求中发生错误时触发

意念 index1—创建闭包时在循环中建立索引 index2—容器中已执行操作的索引 您需要创建带有闭包的容器。此容器将保存所有闭包。容器将检查index1==index2是否在index1之前和之后运行所有操作,如果index1+1>存在

因此,这个容器将检查接收到的闭包的顺序,并按升序逐个运行闭包

细节 代码9.4.1,Swift 4.1

容器 完整代码 别忘了在这里添加容器的代码

后果 解决方案:使用调度信号量和调度队列

let dispatchQueue = DispatchQueue(label: "com.test.Queue", qos: .userInteractive)
我没有保存闭包,而是决定将所有内容封装在调度队列中,并在其中使用信号量

//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")
}
输出总是这样吗

您的问题中没有任何内容表明为什么回答可能会以不同的顺序完成。事实上,你的问题除了简单的打印外,没有显示任何回答的用途。是的,我在做一个简单的测试,没有在完成时使用回答。这个问题只是为了确保服务应该以串行顺序优雅地执行,而不是异步地执行响应,这是不会发生的。我有一些方法可以确保串行响应,比如在第1次完成时调用其他服务,但我想知道是否可以在没有这种方法的情况下完成。是的,并非所有情况下都存在dispatchGroup.leave。我应该全力以赴。这是正确的。关于序列顺序,我相信在数组中保持闭包将是解决方案之一,正如Vasily在回答中所讨论的那样。但它可能会导致延迟,因为我们正在使用信号量等待。延迟来自序列化请求。除非您需要按照特定的顺序执行请求,因为后续请求取决于先前请求的结果,否则最好只触发所有请求并处理无序响应。这可以做到,但使用信号量好吗?或者,如果我们可以在控制器本身中跟踪数组中的闭包,并在所有服务完成后返回该数组?是的,这对智能手机有好处。是的,如果您想等待所有连接何时完成,然后运行完成块,您将花费更多的用户时间。因为互联网连接可能很慢。使用此解决方案,您将获得部分服务器数据,您可以显示这些数据。@RajanMaheshwari信号量是否是当前要使用的等待对象有待讨论,但对我来说是可行的。为了回答您关于跟踪闭包的问题,我在尝试使用YouTube API更新YouTube视频元数据时遇到了一个奇怪的问题,在这种情况下,跟踪闭包将不起作用,但使用信号量会起作用。仅供参考。在一个失败案例之后,我如何取消其余请求?@Hamed这很复杂。例如,您有请求[a、b、c]。如果a失败,您想取消b、c,但同时启动所有b、c。这意味着,您必须在公共空间中保留[a,b,c]对请求的引用,才能从任何单个请求访问[a,b,c]。当a失败时,在a的完成块中,你们必须取消b,c。您必须在每个请求中实现这个取消逻辑,[a,b,c]必须是线程安全的。所以,这不是解决方案。这是唯一的想法,迪尔
在哪里可以找到你问题的答案。我可以中途取消这个操作吗,比如说在5点或按一下按钮。@ShivamTripathi没有办法停止信号灯。除了发出信号外,没有取消信号灯等待操作。要停止buttonClick上的信号量,只需发送信号量并获取一个bool变量,该变量也将出现在api执行代码中。该变量将检查是否进一步执行for循环。因此,它类似于,如果semaphoreStopped{return}来自函数,其中semaphoreStopped是一个布尔值,在按钮上为trueclick@RajanMaheshwari关于您的回答,我可以问一个与DispatchGroup和DispatchQueue相关的问题吗?@Tekhe Yes
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
            }
        }
    }
}
//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")
}