Handle timeout with Alamofire - swift

Is it possible to add timeout handler for Alamofire request?
In my project I use Alamofire this way:
init() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 30
self.alamofireManager = Alamofire.Manager(configuration: configuration)
}
func requestAuthorizationWithEmail(email:NSString, password:NSString, completion: (result: RequestResult) -> Void) {
self.alamofireManager!.request(.POST, "myURL", parameters:["email": email, "password":password])
.responseJSON { response in
switch response.result {
case .Success(let JSON):
//do json stuff
case .Failure(let error):
print("\n\nAuth request failed with error:\n \(error)")
completion(result: .ConnectionFailed)
}
}
}
EDIT:
request fail message
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x7fc10b937320 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=url, NSErrorFailingURLKey=url, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}

You can compare error._code and if it is equal to -1001 which is NSURLErrorTimedOut then you know this was a timeout.
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 120
manager.request("yourUrl", method: .post, parameters: ["parameterKey": "value"])
.responseJSON {
response in
switch (response.result) {
case .success: // succes path
case .failure(let error):
if error._code == NSURLErrorTimedOut {
print("Request timeout!")
}
}
}

Swift 3
The accepted answer didn't work for me.
After a lot of research, I did it like this:
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 120
manager.request("yourUrl", method: .post, parameters: ["parameterKey": "value"])

Swift 3, Alamofire 4.5.0
I wanted to set the same timeout for every HTTP call in my project.
The key idea is to declare the Alamofire Session Manager as a global variable. Then to create a URLSessionConfiguration variable, set its timeout in seconds and assign it to the manager.
Every call in the project can use this configured session manager.
In my case the global Alamofire Session Manager variable was set in AppDelegate file (globally) and its configuration was managed in its didFinishLaunchingWithOptions method
AppDelegate.swift
import UIKit
var AFManager = SessionManager()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 4 // seconds
configuration.timeoutIntervalForResource = 4 //seconds
AFManager = Alamofire.SessionManager(configuration: configuration)
return true
}
...
}
From now the Alamofire request function can be called from any part of the app using the afManager.
For example:
AFManager.request("yourURL", method: .post, parameters: parameters, encoding: JSONEncoding.default).validate().responseJSON { response in
...
}

Swift 3.x
class NetworkHelper {
static let shared = NetworkHelper()
var manager: SessionManager {
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 10
return manager
}
func postJSONData( withParams parameters: Dictionary<String, Any>, toUrl urlString: String, completion: #escaping (_ error: Error,_ responseBody: Dictionary<String, AnyObject>?)->()) {
manager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
if let error = response.result.error {
if error._code == NSURLErrorTimedOut {
print("Time out occurs!")
}
}
}
}
}

Swift 5, Alamofire 5
The cleanest way I found, that works with the latest version of Alamofire is the following:
AF.request(url).response { (dataResponse: AFDataResponse<Data?>) in
switch dataResponse.result {
case .success(let data):
// succes path
case .failure(let error):
switch error {
case .sessionTaskFailed(URLError.timedOut):
print("Request timeout!")
default:
print("Other error!")
}
}
}

Swift 3.x
Accepted answer didn't worked for me too.
This work for me!
let url = URL(string: "yourStringUrl")!
var urlRequest = URLRequest(url: url)
urlRequest.timeoutInterval = 5 // or what you want
And after:
Alamofire.request(urlRequest).response(completionHandler: { (response) in
/// code here
}

Swift 4
This my way and timeout feature is workable, meanwhile practices singleton for api class.
reference from here
struct AlamofireManager {
static let shared: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 5
let sessionManager = Alamofire.SessionManager(configuration: configuration, delegate: SessionDelegate(), serverTrustPolicyManager: nil)
return sessionManager
}()
}
class Auth {
static let api = Auth()
private init() {}
func headers() -> HTTPHeaders {
return [
"Accept": "XXX",
"Authorization": "XXX",
"Content-Type": "XXX"
]
}
func querySample() {
AlamofireManager.shared.request("api_post_url", method: .post, parameters: ["parametersKey": "value"], encoding: JSONEncoding.default, headers: headers())
.responseJSON(queue: DispatchQueue.global(), options: []) { (response) in
switch response.result {
case .success(let value):
// do your statement
case .failure(let error):
if error._code == NSURLErrorTimedOut {
// timeout error statement
} else {
// other error statement
}
}
})
}
func queryOtherSample() {
AlamofireManager.shared.request("api_get_url", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers())
.responseJSON(queue: DispatchQueue.global(), options: []) { (response) in
switch response.result {
case .success(let value):
// do your statement
case .failure(let error):
if error._code == NSURLErrorTimedOut {
// timeout error statement
} else {
// other error statement
}
}
})
}
}

For Swift 3.x / Swift 4.0 / Swift 5.0 users with Alamofire >= 5.0
Used request modifier to increase and decrease the timeout interval.
Alamofire's request creation methods offer the most common parameters for customization but sometimes those just aren't enough. The URLRequests created from the passed values can be modified by using a RequestModifier closure when creating requests. For example, to set the URLRequest's timeoutInterval to 120 seconds, modify the request in the closure.
var manager = Session.default
manager.request(urlString, method: method, parameters: dict, headers: headers, requestModifier: { $0.timeoutInterval = 120 }).validate().responseJSON { response in
OR
RequestModifiers also work with trailing closure syntax.
var manager = Session.default
manager.request("https://httpbin.org/get") { urlRequest in
urlRequest.timeoutInterval = 60
urlRequest.allowsConstrainedNetworkAccess = false
}
.response(...)
You can also check it here

Make extension of SessionManager and write a public static variable like this,
"requestTimeOutInterval" this is a public variable. it has time.
extension SessionManager {
public static let custom: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = requestTimeOutInterval
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
}

Swift 5.0, Alamofire 5.4.2
The error code when time out always equal to NSURLErrorTimedOut, so
I try to retrieve Error object from AFError and upcast to NSError.
extension AFError {
var isTimeout: Bool {
if isSessionTaskError,
let error = underlyingError as NSError?,
error.code == NSURLErrorTimedOut //-1001
{
return true
}
return false
}
}
Invoke on response closure.
let request = URLRequest(url: URL(string: "https://httpbin.org/delay/10")!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 2)
AF.request(request).responseString(completionHandler: { response in
switch response.result {
case .success(_):
print("success")
case .failure(let error):
if error.isTimeout {
print("Timeout!")
}
}
})

In Alamofire 5.5 SessionManager has been renamed Session
Here is the documentation link
https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%205.0%20Migration%20Guide.md#breaking-api-changes
Also the example of user
let manager = Alamofire.Session.default
manager.session.configuration.timeoutIntervalForRequest = 15
let headers: HTTPHeaders? = token == nil ? nil : [.authorization(bearerToken: token!),.accept("application/json")]
manager.request(path, method: method, parameters: parameter, headers: headers).responseJSON { (response) in
switch response.result {
case .success:
case .failure:
}
}

Related

Alamofire: How to conditionally change cache policy based on network status?

The end result I would like to achieve is to use cached data when the network is unavailable and load data from the server when the network is available.
The closest thing I've found is this thread, but I am still having trouble getting it to work using Alamofire.
How to cache response in iOS and show only when internet is not available?
I have two classes, one to check the network status and another that configures an Alamofire url session. Detecting the network status seems to be working, but it's not changing the session configuration. What am I doing wrong? Thanks!
NetworkReachability.shared.startNetworkMonitoring() is run when the app loads in didFinishLaunchingWithOptions.
class NetworkReachability {
static let shared = NetworkReachability()
let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")
func startNetworkMonitoring() {
reachabilityManager?.startListening(onUpdatePerforming: { status in
switch status {
case .notReachable:
ApiManager.configuration.requestCachePolicy = .returnCacheDataDontLoad
case .reachable(.cellular):
ApiManager.configuration.requestCachePolicy = .reloadIgnoringCacheData
case .reachable(.ethernetOrWiFi):
ApiManager.configuration.requestCachePolicy = .reloadIgnoringCacheData
case .unknown:
print("Unknown network state")
}
})
}
}
A custom api manager to configure the cache.
class ApiManager {
static let shared = ApiManager()
static let configuration = URLSessionConfiguration.af.default
public let sessionManager: Alamofire.Session = {
let responseCacher = ResponseCacher(behavior: .modify { _, response in
let userInfo = ["date": Date()]
return CachedURLResponse(
response: response.response,
data: response.data,
userInfo: userInfo,
storagePolicy: .allowed)
})
return Session(
configuration: configuration,
cachedResponseHandler: responseCacher)
}()
}
I'm writing my APIClient here which I've written using Alamofire.
import Alamofire
class NetworkLogger: EventMonitor {
let queue = DispatchQueue(label: "com.ketan.almofire.networklogger")
func requestDidFinish(_ request: Request) {
print(request.description)
}
func request<Value>(
_ request: DataRequest,
didParseResponse response: DataResponse<Value, AFError>
) {
guard let data = response.data else {
return
}
if let json = try? JSONSerialization
.jsonObject(with: data, options: .mutableContainers) {
print(json)
}
}
}
class APIClient {
private var manager = Session()
init() {
configureSession()
}
private func manageCachePolicy() {
if NetworkReachabilityManager()?.isReachable ?? false {
manager.sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
} else {
manager.sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
}
}
private func configureSession() {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
if NetworkReachabilityManager()?.isReachable ?? false {
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
}
manager = Session(configuration: configuration, eventMonitors: [NetworkLogger()])
///When we don't want network logs
// manager = Session(configuration: configuration)
}
// MARK: - Request
func requestData(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
completion: #escaping (Result<Data, ErrorResult>) -> Void) {
manageCachePolicy()
manager.request(convertible,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers).validate().responseData
{ (response) in
switch response.result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(.network(string: error.localizedDescription)))
}
}
}
}

Alamofire - How to get API error from AFError

In my quest to implement Alamofire 5 correctly and handle custom error model responses, I have yet to find an accepted answer that has an example.
To be as thorough as possible, here is my apiclient
class APIClient {
static let sessionManager: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true
return Session(configuration: configuration, eventMonitors: [APILogger()])
}()
#discardableResult
private static func performRequest<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (Result<T, AFError>)->Void) -> DataRequest {
return sessionManager.request(route)
// .validate(statusCode: 200..<300) // This will kill the server side error response...
.responseDecodable (decoder: decoder){ (response: DataResponse<T, AFError>) in
completion(response.result)
}
}
static func login(username: String, password: String, completion:#escaping (Result<User, AFError>)->Void) {
performRequest(route: APIRouter.login(username: username, password: password), completion: completion)
}
}
I am using it like this
APIClient.login(username: "", password: "") { result in
debugPrint(result)
switch result {
case .success(let user):
debugPrint("__________SUCCESS__________")
case .failure(let error):
debugPrint("__________FAILURE__________")
debugPrint(error.localizedDescription)
}
}
I have noticed that if I use .validate() that the calling function will receive a failure however the response data is missing. Looking around it was noted here and here to cast underlyingError but thats nil.
The server responds with a parsable error model that I need at the calling function level. It would be far more pleasant to deserialize the JSON at the apiclient level and return it back to the calling function as a failure.
{
"errorObject": {
"summary": "",
"details": [{
...
}]
}
}
UPDATE
Thanks to #GIJoeCodes comment I implemented this similar solution using the router.
class APIClient {
static let sessionManager: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true
return Session(configuration: configuration, eventMonitors: [APILogger()])
}()
#discardableResult
private static func performRequest<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (_ response: T?, _ error: Error?)->Void) {
sessionManager.request(route)
.validate(statusCode: 200..<300) // This will kill the server side error response...
.validate(contentType: ["application/json"])
.responseJSON { response in
guard let data = response.data else { return }
do {
switch response.result {
case .success:
let object = try decoder.decode(T.self, from: data)
completion(object, nil)
case .failure:
let error = try decoder.decode(ErrorWrapper.self, from: data)
completion(nil, error.error)
}
} catch {
debugPrint(error)
}
}
}
// MARK: - Authentication
static func login(username: String, password: String, completion:#escaping (_ response: User?, _ error: Error?)->Void) {
performRequest(route: APIRouter.login(username: username, password: password), completion: completion)
}
}
Called like this
APIClient.login(username: "", password: "") { (user, error) in
if let error = error {
debugPrint("__________FAILURE__________")
debugPrint(error)
return
}
if let user = user {
debugPrint("__________SUCCESS__________")
debugPrint(user)
}
}
This is how I get the errors and customize my error messages. In the validation, I get the errors outside of the 200..<300 response:
AF.request(
url,
method: .post,
parameters: json,
encoder: JSONParameterEncoder.prettyPrinted,
headers: headers
).validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseJSON { response in
switch response.result {
case .success(let result):
let json = JSON(result)
onSuccess()
case .failure(let error):
guard let data = response.data else { return }
do {
let json = try JSON(data: data)
let message = json["message"]
onError(message.rawValue as! String)
} catch {
print(error)
}
onError(error.localizedDescription)
}
debugPrint(response)
}
First, there's no need to use responseJSON if you already have a Decodable model. You're doing unnecessary work by decoding the response data multiple times. Use responseDecodable and provide your Decodable type, in this case your generic T. responseDecodable(of: T).
Second, wrapping your expected Decodable types in an enum is a typical approach to solving this problem. For instance:
enum APIResponse<T: Decodable> {
case success(T)
case failure(APIError)
}
Then implement APIResponse's Decodable to try to parse either the successful type or APIError (there are a lot of examples of this). You can then parse your response using responseDecodable(of: APIResponse<T>.self).

how to set timeout in alamofire with DataRequest

let api = request(url, method: .post, parameters:params, headers:nil)
api.responseJSON { (responseData) -> Void in
//code
}
I don't have time to do code refactoring.
So I want to set the timeout based on the code above, how can I do it?
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 12
configuration.timeoutIntervalForResource = 12
let alamoFireManager = Alamofire.Session.init(configuration: configuration)
alamoFireManager.request(url, method: .post, parameters: params).responseJSON { response in
switch (response.result) {
case .success:
break
case .failure(let error):
print(error)
break
}
}

the alamofire isn't doing a proper connection to the api

i am trying to get a response that contain json from openweathermap api,
but from the debugger it seems that when he get to the code block of alamofire its just skips it
here is the block of code that i am using for this task
func printJson(){
Alamofire.request("https://api.openweathermap.org/data/2.5/find?appid=6ad1f143546b8c6d761ecbda6a34a005&q=yavne", method: .get).responseJSON { response in
if response.data != nil {
var json : JSON
do{
json = try JSON(data: response.data!)
print(json)
}catch{}
}
}
}
Use this helper class to make API calls.
class NetworkHandler {
static let shared = NetworkHandler()
func sendRequest(withUrl url:String,andBody body:[String:Any]? = [:],
completionHandler: #escaping ((_ status: Bool,_ response:Any) -> Void)) {
DispatchQueue.global(qos: .userInitiated).async {
Alamofire.request(url, method: .post, parameters: body,encoding: JSONEncoding.default, headers: nil).responseJSON {
response in
guard response.response?.statusCode == 200 else{
completionHandler(false,"Service Temporarily Unavailable.\nPlease check your internet connection or contact your Service provider.")
return
}
switch response.result {
case .success:
completionHandler(true,response.value!)
break
case .failure(let error):
completionHandler(false,error.localizedDescription)
}
}
}
}
}

How to execute alamofire background upload request?

I need to send zip file to server side.
There is my request which I need to work in background
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 10 // seconds
alamoFireManager = Alamofire.SessionManager(configuration: configuration)
appDeligate.log.debug("request was sended")
alamoFireManager.upload(deligate.data!,
to: deligate.url,
method: .post,
headers: headers)
.uploadProgress(closure: {
progress in
print("Upload Progress: \(progress.fractionCompleted)")
})
.validate()
.responseJSON {}
Now it is work properly, but I need to execute this in background. According to the Alamofire README doc
https://github.com/Alamofire/Alamofire
it says
Creating a Session Manager with Background Configuration
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
I changed my configuration to correspond background configuration
now it looks like this
let configuration = URLSessionConfiguration.background(withIdentifier: "com.room.myApp")
configuration.timeoutIntervalForRequest = 10 // seconds
alamoFireManager = Alamofire.SessionManager(configuration: configuration)
alamoFireManager.upload(deligate.data!,
to: deligate.url,
method: .post,
headers: headers)
.uploadProgress(closure: {
progress in
print("Upload Progress: \(progress.fractionCompleted)")
})
.validate()
.responseJSON {}
And I get error
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Upload tasks from NSData are not supported in background sessions.'
*** First throw call stack:
(0x18ec511b8 0x18d68855c 0x18f33808c 0x18f33796c 0x18f336e68 0x100ef9218 0x100f05dc8 0x18f336dc8 0x18f3378cc 0x100255890 0x1002518e8 0x100234200 0x100234448 0x100ef9218 0x100f05dc8 0x100233fc4 0x100255290 0x10029d238 0x10029ae4c 0x10029ac34 0x10006dd78 0x100071044 0x100082708 0x10002b310 0x100ef9258 0x100ef9218 0x100f0726c 0x100f08e2c 0x100f08b78 0x18dce32a0 0x18dce2d8c)
libc++abi.dylib: terminating with uncaught exception of type NSException
What am I doing wrong?
Is it issue from my side or lib side?
Feel free to ask
Edit
there is sending flow
There is how I create zip file
internal func madeRequest() {
DispatchQueue.global().async {
if self.createZipPhotoDir() {
self.appDeligate.log.debug("zip dir was created")
self.serverConnection.makeServerRequest()
} else {
self.appDeligate.log.error("can NOT execute createZipPhotoDir()")
}
}
}
private func createZipPhotoDir() -> Bool {
let withContentsOfDirectory: String! = UtilDirectory.pathToMyCurrentAvatarDir.tweak() // "/var/mobile/Containers/Data/Application/739A895E-7BCA-47A8-911F-70FBC812CEB3/Documents/default#domein.com/AvatarPackage/name/"
let atPath: String! = UtilDirectory.tempZipPath.tweak() // "/var/mobile/Containers/Data/Application/739A895E-7BCA-47A8-911F-70FBC812CEB3/Documents/default#domein.com/tmpZipDir.zip"
return SSZipArchive.createZipFile(atPath: atPath, withContentsOfDirectory: withContentsOfDirectory)
}
zip file is creating ok
Then I make server request
required init() {
configureAlamofireManager()
}
private func configureAlamofireManager() {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.fittingroom.newtimezone.Fitzz")
alamoFireManager = Alamofire.SessionManager(configuration: configuration)
}
internal func makeServerRequest() {
appDeligate.log.debug("request was sended")
alamoFireManager.upload(deligate.data!,
to: deligate.url,
method: .post,
headers: headers)
.uploadProgress(closure: {
progress in
print("Upload Progress: \(progress.fractionCompleted)")
})
.validate()
.responseJSON {
[weak self]
response in
self?.appDeligate.log.debug("response : \(response)")
self?.appDeligate.log.debug(String(describing: response.timeline))
switch response.result {
case .success(let value):
self?.appDeligate.log.debug("succes in server connection response")
let result = self?.getStatusCodeAndData(json: JSON(value))
self?.deligate.onSuccess(statusCode: result?.statusCode, data: result?.data)
case .failure(let error):
self?.appDeligate.log.error("error in UploadingRequest : \(error)")
self?.deligate.onError()
}
}
}
There is a way how I get data to send
internal var data: Data {
var data = Data()
let filePath = UtilDirectory.tempZipPath.tweak()
if let result = UtilFile.exists(path: filePath), result.isFileExist == true, result.whatIsIt == .file {
if let fileData = FileManager.default.contents(atPath: filePath) {
data = fileData
appDeligate.log.debug("*** DATA : \(data) ***")
} else {
print("Could not parse the file")
}
} else {
appDeligate.log.error("some ERROR here: file not exist")
}
return data
}
from Background Transfer Considerations
:
Only upload tasks from a file are supported (uploading from data objects or a stream will fail after the program exits).
that means it is limitation from NSURLSession - you need you upload from a file and then try to solve the other error with file
Update
appDeligate.log.debug("request was sended")
let tempZipFilePath = UtilDirectory.tempZipPath.tweak()
alamoFireManager.upload(tempZipFilePath,
to: deligate.url,
method: .post,
headers: headers)
did you see this section Open Radars:
Open Radars
The following radars have some effect on the current implementation of Alamofire.
rdar://26870455 - Background URL Session Configurations do not work in the simulator
Use below code once, Its working for me
import Alamofire
var sessionManager: Alamofire.SessionManager
var backgroundSessionManager: Alamofire.SessionManager
self.sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
self.backgroundSessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.background(withIdentifier: "com.youApp.identifier.backgroundtransfer"))
backgroundSessionManager.upload(multipartFormData: blockFormData!, usingThreshold: UInt64.init(), to: url, method: .post, headers: APIManager.headers(), encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.uploadProgress {
(progress) in
let p = progress.fractionCompleted * 100
uploadProgress(p)
}
upload.responseJSON { response in
switch(response.result) {
case .success(let JSON):
DispatchQueue.main.async {
print(JSON)
}
case .failure(let error):
DispatchQueue.main.async {
print(error)
}
}
}
case .failure(let error):
DispatchQueue.main.async {
print(error)
}
}
})