URLSession.shared.datatask 和 dispatchgroup 的问题
issue with URLSession.shared.datatask and dispatchgroup
我对 swift 还是很陌生,所以请多多包涵。我有一个问题,这个应用程序在存档和网守签名后在我的开发机器上运行良好。但在其他用户机器上,它无法 return 变量。我想让它工作,所以捕获的东西可能需要一些工作,就像我在 swift
上说的我非常 new/green
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()
}
}
我已经尝试为此创建一个调度队列 运行 异步调度队列中的整个块,我尝试过同步,我尝试了一些疯狂的事情,但似乎没有就 return 变量而言,在用户机器上工作,它似乎并不等待。等待我的并且工作完美......我在这里有点困惑,api调用部分在帮助下被重写了几次,因为它最初是锁定用户机器上的线程并触发线程安全,现在它只是returns 一个 nil 变量并弹出我的自定义错误消息...
请...帮助....
您应该放弃这种尝试使用分派组来使固有异步 API 行为同步的方法。通常,建议您完全避免使用 wait
(无论是调度组还是信号量或其他)。 wait
方法本质上是低效的(它阻塞了一个非常有限的工作线程)并且应该只由后台队列使用,如果你真的需要它,这里不是这种情况。
但是如果你在移动平台上阻塞主线程,不立即响应并且是同步的方法会导致非常糟糕的用户体验(应用程序将在此同步方法期间冻结),更糟糕的是,OS 如果您在错误的时间执行此操作,看门狗进程甚至可能会终止您的应用程序。
您正在调用异步方法,因此您的方法也应该采用异步模式。例如。与其尝试 return 值,不如为您的方法提供一个 @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 版本,因此如果您需要支持较旧的 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()
}
如果你在所有这些之后仍然有“线程安全”问题,我建议你单独研究,如果你还有问题,post small reproducible example单独的问题。但是线程安全和异步模式是非常不同的问题。
我对 swift 还是很陌生,所以请多多包涵。我有一个问题,这个应用程序在存档和网守签名后在我的开发机器上运行良好。但在其他用户机器上,它无法 return 变量。我想让它工作,所以捕获的东西可能需要一些工作,就像我在 swift
上说的我非常 new/green
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()
}
}
我已经尝试为此创建一个调度队列 运行 异步调度队列中的整个块,我尝试过同步,我尝试了一些疯狂的事情,但似乎没有就 return 变量而言,在用户机器上工作,它似乎并不等待。等待我的并且工作完美......我在这里有点困惑,api调用部分在帮助下被重写了几次,因为它最初是锁定用户机器上的线程并触发线程安全,现在它只是returns 一个 nil 变量并弹出我的自定义错误消息...
请...帮助....
您应该放弃这种尝试使用分派组来使固有异步 API 行为同步的方法。通常,建议您完全避免使用 wait
(无论是调度组还是信号量或其他)。 wait
方法本质上是低效的(它阻塞了一个非常有限的工作线程)并且应该只由后台队列使用,如果你真的需要它,这里不是这种情况。
但是如果你在移动平台上阻塞主线程,不立即响应并且是同步的方法会导致非常糟糕的用户体验(应用程序将在此同步方法期间冻结),更糟糕的是,OS 如果您在错误的时间执行此操作,看门狗进程甚至可能会终止您的应用程序。
您正在调用异步方法,因此您的方法也应该采用异步模式。例如。与其尝试 return 值,不如为您的方法提供一个 @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 版本,因此如果您需要支持较旧的 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()
}
如果你在所有这些之后仍然有“线程安全”问题,我建议你单独研究,如果你还有问题,post small reproducible example单独的问题。但是线程安全和异步模式是非常不同的问题。