在Swift中以串行顺序同步多个Web服务调用

eoigrqb6  于 2023-11-16  发布在  Swift
关注(0)|答案(4)|浏览(168)

我点击一个web服务url 10次并得到响应。我使用AlamofireSwiftyJSON。这是我的控制器代码

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
            }
        }
    }
}


我发送了一个带有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 
}


那么我们也不会以串行顺序获得响应,因为在dtoc和dispatchQueue中的网络调用假设任务已经完成。
条件是在不冻结UI的情况下,只以异步方式命中服务。如果我以同步方式命中服务,那么我会得到我想要的结果。但是阻塞主线程是完全不能接受的。
我可以使用数组或一些全局bool变量来管理这个东西,但我不想使用它们。有没有其他方法可以让我以串行顺序获得响应?任何帮助或提示都很感激。

mzillmmw

mzillmmw1#

解决方案:使用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")
}

字符串

输出始终为this


的数据

uqdfh47h

uqdfh47h2#

创意

*index 1-创建闭包时,循环中的索引
*index 2-容器中已执行操作的索引

您需要创建一个包含闭包的容器,这个容器会保存所有的闭包。容器会检查index1 == index2是否运行了index 1之前和if index1 + 1 > exist之后的所有操作。
因此,这个容器将检查接收到的闭包的顺序,并以升序逐个运行闭包。

详细数据

Xcode 9.4.1,Swift 4.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()
    }
}

字符串

完整代码

不要忘记在这里添加容器的代码

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
            }
        }
    }
}

结果

x1c 0d1x的数据

ru9i0ody

ru9i0ody3#

按顺序执行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在一个或多个请求中发生错误时永远不会触发。

3zwtqj6y

3zwtqj6y4#

可以在多个地方轻松使用的代码:

extension Array where Element:Equatable {
func syncedExecution(execute:@escaping ((Element, @escaping (() -> Void)) -> ()), completion: (() -> Void)? = nil) {
    let dispatchQueue = DispatchQueue(label: UUID().uuidString, qos: .background)
    let dispatchSemaphore = DispatchSemaphore(value: 0)
    dispatchQueue.async {
        for item in self {
            execute(item) {
                dispatchSemaphore.signal()
                if item == last {
                    completion?()
                }
            }
            dispatchSemaphore.wait()
        }
    }
}
}

字符串
使用方法:

itemsArr. syncedExecution { item, signal in
    item.someApiRequest { success in
        // do something
        signal()
    }
} completion: {
        // all tasks completed
    }

相关问题