首页 > 解决方案 > URLSession.shared.datatask 和 dispatchgroup 的问题

问题描述

我对 swift 还很陌生,所以请耐心等待。我遇到了一个问题,这个应用程序在存档后在我的开发机器上运行良好,并且网守签名。但在其他用户机器上,它无法返回变量。我想让它工作,所以捕获的东西可能需要一些工作,就像我说我在 swift 上非常新/绿色


    enum apiCalls: Error {
        case badRequest
    }
    
    func CheckPortSpeeds() {
        if NetMon.shared.isConnected == true {
            guard let url = URL(string: MdnUrl) else {
                return
            }
            var request = URLRequest(url: url )
            request.httpMethod = "POST"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.setValue("application/json", forHTTPHeaderField: "Accept")
            let data = ["jsonrpc": "2.0", "method": "runCmds", "params": ["version": 1, "cmds": ["enable", "show interfaces ethernet1/1-30/1 status"], "format": "json"], "id": 1] as [String : Any]
            request.httpBody = try! JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted)
            var response : JSON
            var status : Int
            do {
                (response, status) = try APICaller().getResults(request: request)
            }
            catch apiCalls.badRequest {
                status = 400
                response = ["nil": "nil"]
                Alerts.shared.AlertUser(Message: "Host Not Reachable", Info: "Api Module")
            }
            catch SwiftyJSONError.notExist {
                status = 400
                response = ["nil": "nil"]
                Alerts.shared.AlertUser(Message: "Bad JSON response from host", Info: "Api Module")
            }
            catch {
                status = 400
                response = ["nil": "nil"]
                Alerts.shared.AlertUser(Message: "Unknown Error", Info: "Api Module")
            }
            if status == 200 {
                for n in 1...30 {
                    InterfaceDict["eth\(n)"] = Dictionary<String, String>()
                    InterfaceDict["eth\(n)"]?["portSpeed"] = response["result"][1]["interfaceStatuses"]["Ethernet\(n)/1"]["bandwidth"].int64
                    InterfaceDict["eth\(n)"]?["optic"] = response["result"][1]["interfaceStatuses"]["Ethernet\(n)/1"]["interfaceType"].string
                    guard let optic = InterfaceDict["eth\(n)"]?["optic"] as? String else {
                        return
                    }
                    InstalledOptic[n] = optic
                    guard let prtspd = InterfaceDict["eth\(n)"]?["portSpeed"] as? Int64 else {
                        return
                    }
                    PortSpeed[n] = prtspd
                }
                PortHandler().PortFlopper()
                ContentView().replies.responses += 1
            
                for r in 1...30 {
                    if PortMatch[r] == false {
                        FlipPorts(port: r, optic: InstalledOptic[r]!)
                    }
                }
            }
        }
        else{
            Alerts.shared.AlertUser(Message: "Network connection is down, please check netwok conection and try again", Info: "Network Monitor")
        }
    }

以上基本上是设置api调用等。

下面是我对 Arista 交换机的 eapi 进行 api 调用的地方


import Cocoa
import SwiftyJSON

class APICaller: NSObject {
    

    enum apiCalls: Error {
        case badRequest
    }
    
    func getResults(request: URLRequest) throws -> (JSON, Int) {
        var result: Result<(JSON, Int), Error> = .failure(SwiftyJSONError.notExist)
        let group = DispatchGroup()
        group.enter()
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            result = Result(catching: {
                if let e = error { throw e }
                guard let status = (response as? HTTPURLResponse)?.statusCode else {
                    throw apiCalls.badRequest
                }
                let json = try JSON(data: data ?? Data())
                return (json, status)
            })
            group.leave()
        }
        task.resume()
        group.wait()
        return try result.get()
    }
}

我已经尝试为此创建一个调度队列并在调度队列中以异步方式运行整个块,我尝试过同步,我尝试了一些疯狂的事情,就返回而言,它似乎不适用于用户机器变量,它似乎并没有等待。等待我的并且完美地工作......我在这里有点困惑,apicall部分已经在帮助下重写了几次,因为它最初锁定用户机器上的线程并触发线程安全,现在它只返回一个nil变量和弹出我的自定义错误消息...

请帮忙....

标签: swift5urlsessiondispatchgroup

解决方案


您应该放弃这种尝试使用调度组来使固有异步 API 同步运行的方法。一般来说,建议您完全避免使用wait(无论是调度组还是信号量或其他)。该wait方法本质上是低效的(它阻塞了非常有限的工作线程之一)并且应该只由后台队列使用,如果你真的需要它,这里不是这种情况。

但是如果你在移动平台上阻塞了主线程,不立即响应并且同步的方法可能会导致非常糟糕的用户体验(应用程序将在此同步方法期间冻结),更糟糕的是,操作系统看门狗进程甚至可能终止你的应用程序如果你在错误的时间这样做。

您正在调用异步方法,因此您的方法也应该使用异步模式。例如,与其尝试返回值,不如给您的方法一个@escaping闭包参数,并在网络请求完成时调用该闭包。

考虑getResults:您已经在那里雇用了一个Result<(JSON, Int), Error>。所以让我们用它作为闭包的参数类型:

enum ApiError: Error {      // type names should start with upper case letter
    case badRequest
}

func getResults(request: URLRequest, completion: @escaping (Result<(JSON, Int), Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            completion(.failure(error))
            return
        }

        guard
            let status = (response as? HTTPURLResponse)?.statusCode,
            let data = data
        else {
            completion(.failure(ApiError.badRequest))
            return
        }

        do {
            let json = try JSON(data: data)
            completion(.success((json, status)))
        } catch let parseError {
            completion(.failure(parseError))
        }
    }
    task.resume()
}

完成后,您将像这样使用它:

getResults(request: request) { result in
    switch result {
    case let .failure(error):
        // do something with error
    case let .success((json, statusCode)):
        // do something with json and statusCode
    }
}

// but do not use error, json, or statusCode here, because the above runs asynchronous (i.e. later)

请注意,如果您的目标是 iOS 15 或 macOS 12,Swift 现在有一个更直观的异步模式(称为 async/await),但我假设您正在尝试针对更广泛的 OS 版本,所以以上是传统技术,如果您需要支持较旧的操作系统版本。


顺便说一句,您提到它会引发有关“线程安全”的错误。这是一个非常不同的问题,可能是因为您尝试从后台线程进行 UI 更新,或者在从多个线程更新某些共享属性时存在一些线程安全违规。

一个非常常见的模式是确保我们将完成处理程序分派回主线程。如果我们将所有内容分派回主队列,则很难引入线程安全问题或从错误的线程更新 UI。

例如,在上述方法的以下再现中,我将成功和失败条件分派回主队列:

func getResults(request: URLRequest, completion: @escaping (Result<(JSON, Int), Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        do {
            if let error = error { throw error }

            guard
                let status = (response as? HTTPURLResponse)?.statusCode,
                let data = data
            else {
                throw ApiError.badRequest
            }

            let json = try JSON(data: data)
            DispatchQueue.main.async {
                completion(.success((json, status)))
            }
        } catch let error {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
        }
    }
    task.resume()
}

如果您在所有这些之后仍然存在“线程安全”问题,我建议您单独研究,如果您仍有问题,请在单独的问题中发布一个小的可重复示例。但是线程安全和异步模式是非常不同的问题。


推荐阅读