I'm making a request to my server with AlamoFire, and I have a setup something like this, where "self.error" is a String variable.
session.request("https://localhost:3000/my-route", method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success(let result):
print("nice", result)
case .failure(let err):
self.error = err //self.error is a String state variable
print("failure")
}
}
However, I get the (sensible) error "Cannot assign value of type 'AFError' to type 'String'", because I'm trying to set self.error = err, and err is of type AFError, whereas self.error is a String state variable.
I realize I could just do something like self.error = "There was an error", but I'd like to provide the user with some feedback as to what the error actually was, and I can't seem to find the correct properties (i.e. something like err.stringValue, err.description, etc.) to do so. Seems like there's some source code here https://github.com/Alamofire/Alamofire/blob/master/Source/AFError.swift but I'm not sure how this relates to the string value I'd like to obtain.
Any help here would be much appreciated.
For such cases you can always use String interpolation e.g. self.error = "\(err)", but this is not a good idea because err is not just a string and could contain something like:
sessionTaskFailed(error: Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the server." UserInfo={_kCFStreamErrorCodeKey=61, NSUnderlyingError=0x280e37750 {Error Domain=kCFErrorDomainCFNetwork Code=-1004 "(null)" UserInfo={_kCFStreamErrorCodeKey=61, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <78576E38-D021-4ADC-87D4-1C9D81FF0E3A>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <78576E38-D021-4ADC-87D4-1C9D81FF0E3A>.<1>" ), NSLocalizedDescription=Could not connect to the server., NSErrorFailingURLStringKey=https://localhost:3000/my-route, NSErrorFailingURLKey=https://localhost:3000/my-route, _kCFStreamErrorDomainKey=1})
Like #Menaim already mentioned, you can use err.localizedDescription, then you could get something like URLSessionTask failed with error: Could not connect to the server.
Generally, I would not pass the error directly to the user. This depends at your users, but do they really should know what e.g. URLSessionTask is?
In my opinion you should define error string for user, that every user can understand and you should log err.localizedDescription to the console, so you can know what is happening.
If your app supports many languages, you can define localizable.strings files and translate the errors.
If you do this print("failure"), every time you have an error, how can you distinguish the errors?
My suggestion would be something like:
case .failure(let err):
self.error = "There was an error connecting to the server"
print("Failed to connect to backend: \(err.localizedDescription)")
You can try err.localizedDescription or you can get the error in the completion of the function and return it completion(error)
Related
I am working on a project where I use CLGeocoder to determine the placemarks for a particular location.
Very simple call like this.
CLGeocoder().geocodeAddressString(location) { (placemarks, error) in
if let error = error {
print(error.localizedDescription)
}
Instead of printing out the NSError's localizedDescription, I would like to be able to capture the CLError code and respond accordingly. For example, if the location can't be found, my localizedDescription prints
The operation couldn’t be completed. (kCLErrorDomain error 8.)
How can I, in Swift determine the CLError code so that I do not have to switch on some localized description that will change based on user's locale?
Can this be done?
I only have a couple of cases that I want to switch on.
You need to cast the error as CLError and then switch the error code:
if let error = error as? CLError {
switch error.code {
case .locationUnknown:
print("locationUnknown: location manager was unable to obtain a location value right now.")
case .denied:
print("denied: user denied access to the location service.")
case .promptDeclined:
print("promptDeclined: user didn’t grant the requested temporary authorization.")
case .network:
print("network: network was unavailable or a network error occurred.")
case .headingFailure:
print("headingFailure: heading could not be determined.")
case .rangingUnavailable:
print("rangingUnavailable: ranging is disabled.")
case .rangingFailure:
print("rangingFailure: a general ranging error occurred.")
default : break
}
}
I am calling some APIs from my app. I'd like to get and process the error code. There seems to be a lot of information on how to retrieve and process the response codes, but very little on the error code. I'd like to access the error code in case, say the user has no internet connection so the call to the API fails. I am using this code:
session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
print("Tide Station API call failed with error \(error)")
return
}
if let response = response as? HTTPURLResponse {
print("Tide Station API call response is \(response.statusCode)")
}
if let data = data
{
do {
let result = try JSONDecoder().decode(TideStations.self, from: data)
self.tideStations = result.stations
print("\(result.stations.count) Tide Stations successfully loaded")
self.showTideStationsOnMap()
} catch {
print("Error while stations parsing: \(error)")
}
}
}).resume()
Which generates the following output in the debug area when my device is is airplane mode, i.e. I am forcing one sure way of the API call failing: (note I have bolded the area from my error handling print statement)
2020-02-26 17:57:58.235368+0000 APITest[5464:2590011] Task <006E7876-214F-41E3-8BDE-26960AF1F554>.<1> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={NSUnderlyingError=0x283a918f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, NSErrorFailingURLKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be offline.}
Astronomical Times API call failed with error Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={NSUnderlyingError=0x283a918f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, NSErrorFailingURLKey=https://api.sunrise-sunset.org/json?lat=57.5081&lng=-1.7841&date=2020-06-21&formatted=0, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be offline.}
In this example, I want to extract from this the fact that the internet connection appears down and advise the user. I would have thought that there would be similar support for error code extraction as there is for result code extraction. Would it be more normal to just say an error occurred (easy to detect) and handle elsewhere (e.g. detect no internet before calling the API)? If so, what shout I be guarding for?
The Error class in swift does not contain the error code and the error domain in it. However, if you print the error you can definitely see the error code and domain in the logs.
You can access those by simply casting the Swift's Error to the old Objective-C NSError. This classes are toll-free bridgeable, so there is no need to check if the cast fails or forcefully perform the cast with as!. Simply do:
let nsError = error as NSError
print("Error code is: \(nsError.code)")
So I am having issues running asynchronous code in the unit tests of my app. I am using expectations in order to wait for the code to execute before the test completes. The asynchronous code runs through heroku in order to get the values and then should return them in the app. With this unit test, I am trying to make sure the connection through heroku is working. Here is a look at my code:
func test() {
let url = "https://s.herokuapp.com/test"
let params: [String: Any] = ["account_id": AppState.sharedInstance.user.accounttoken]
let expectation = self.expectation(description: "Testing returning value")
let totalBalance = ""
Alamofire.request(url, method: .post, parameters: params)
.validate(statusCode: 200..<300)
.responseJSON { response in
switch response.result {
case .success:
print("Returned with success")
case .failure(let error):
let status = response.response?.statusCode
print("Failed, status: \(status)")
print("Here is the error: \(error)")
}
if let result = response.result.value {
let balance = result as! NSDictionary
let totalBalance = String(describing: "\(balance["Balance"]!)")
}
XCTAssert(totalBalance != "")
expectation.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
XCTAssert(totalBalance != "")
}
The reason I am confused is because I have no error getting the asynchronous code to return values in the actual app. I only have the wait time issue in unit testing. I am getting two fail errors, one for the XCTAssert not being true and one for the waitForExpectations going longer than 10 seconds. Here is some of the errors that are popping up as well if that helps find the solution:
Here is the error messages in text form:
2019-07-01 09:44:38.181971-0400 Spotbirdparking[49677:4306598] TIC TCP
Conn Failed [6:0x6000030b7cc0]: 3:-9816 Err(-9816) 2019-07-01
09:44:38.188607-0400 Spotbirdparking[49677:4306598]
NSURLSession/NSURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9816) 2019-07-01 09:44:38.188819-0400
Spotbirdparking[49677:4306598] Task
.<1> HTTP load failed (error
code: -1200 [3:-9816]) 2019-07-01 09:44:38.189215-0400
Spotbirdparking[49677:4306623] Task
.<1> finished with error - code:
-1200 /Users/drewloughran/Desktop/SpotBird/SpotbirdparkingTests/SpotbirdparkingTests.swift:117:
error: -[SpotbirdparkingTests.SpotbirdparkingTests test_stripe] :
Asynchronous wait failed: Exceeded timeout of 10 seconds, with
unfulfilled expectations: "Testing stripe returning value".
I am also fairly new to swift so any help would be appreciated with this problem.
Drew, please check this error first. Your request is failing to load.
HTTP load failed (error code: -1200 [3:-9816]) 2019-07-01 09:44:38.189215-0400 Spotbirdparking[49677:4306623] Task .<1> finished with error - code: -1200
Also make sure that the block you are using to fulfill the expectation is being executed. Probably it's not and that's the reason your expectation is not fulfilled and your test is failing.
Also, from this documentation I can see that your validation intersects an automatic validation case. which is redundant.
Response Validation
By default, Alamofire treats any completed request to be successful, regardless of the content of the response. Calling validate before a response handler causes an error to be generated if the response had an unacceptable status code or MIME type.
Manual Validation
Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
Automatic Validation
Automatically validates status code within 200...299 range, and that the Content-Type header of the response matches the Accept header of the request, if one is provided.
Alamofire.request("https://httpbin.org/get").validate().responseJSON { response in
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
Good luck!
You should not be testing your Alamofire calls by doing actual networking. This causes you to hit the network every time you test; it is unreliable (the network can fail) and unnecessary. We know what the network does! You also do not have to test Alamofire; it isn't yours to test, and again, you know what it does. Therefore you should refactor your code for testing and mock the network call.
Using Alamofire 4/Swift 3 how can you differentiate between a request that fails due to:
Network connectivity (host down, can't reach host) vs
Invalid server HTTP response code (ie: 499) which causes the Alamofire request to fail due to calling validate()?
Code:
sessionManager.request(url, method: .post, parameters:dict, encoding: JSONEncoding.default)
.validate() //Validate status code
.responseData { response in
if response.result.isFailure {
//??NETWORK ERROR OR INVALID SERVER RESPONSE??
}
}
We want to handle each case differently. In the latter case we want to interrogate the response. (In the former we don't as there is no response).
Here's our current working solution:
sessionManager.request(url, method: .post, parameters:dict, encoding: JSONEncoding.default)
.validate() //Validate status code
.responseData { response in
if response.result.isFailure {
if let error = response.result.error as? AFError, error.responseCode == 499 {
//INVALID SESSION RESPONSE
} else {
//NETWORK FAILURE
}
}
}
If result.error it is of type AFError you can use responseCode. From the AFError source comments:
/// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
/// `responseContentType`, and `responseCode` properties will contain the associated values.
public var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
Maybe there is a better way (?) but that seems to work...
Alamofire can tell you about the status of the request,
this code works just fine for me:
if let error = response.result.error as? NSError {
print(error)//print the error description
if (error.code == -1009){
print(error.code) // this will print -1009,somehow it means , there is no internet connection
self.errorCode = error.code
}
//check for other error.code
}else{
//there is no problem
}
the error.code will tell you what is the problem
Automatic validation should consider status code within 200...299 (success codes) range so when you get an invalid server HTTP response code 5xx (499 means Client Closed Request) you are sure it's not depend by validation.
About the statusCode, my advice is to follow the correct new rules to get it. If you have some problem to retrieve it look this SO answer.
Speaking about network reachability you could write:
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.listener = { status in
print("Network Status Changed: \(status)")
}
manager?.startListening()
There are some important things to remember when using network reachability to determine what to do next.
Do NOT use Reachability to determine if a network request should be
sent. You should ALWAYS send it.
When Reachability is restored, use the event to retry failed network
requests. Even though the network requests may still fail, this is a
good moment to retry them.
The network reachability status can be useful for determining why a
network request may have failed. If a network request fails, it is
more useful to tell the user that the network request failed due to
being offline rather than a more technical error, such as "request
timed out."
You can find these details also in the official Alamofire 4 GitHUB page
I'm just getting acquainted with Alamofire (using Alamofire 4 with Xcode 8, Swift3, iOS 10)
I'm attempting to use HTTP protocol methods on a on a server API given the an ip address of XXX.XXX.X.XX
To take a photo with the SDK I'm using, I must use a GET request. The documentation states it like this:
Take the photo
GET takepic/true
Trigger the camera to take a photo
Return http status code 200 means OK, 500 means failed
The thing is, I'm not sure how to successfully get the data from the server, and also do not know how to use this "takepic/true" property I'm given.
I attempted the issue like so:
Alamofire.request("http://myIPAddress", method: .get).responseJSON { (response) in
if let status = response.response?.statusCode
{
switch(status)
{
case 200:
print("example success")
case 500:
print("The response failed")
default:
print("error with response status: \(status)")
}
}
if let result = response.result.value
{
print(result)
}
}
After running the app, I get a bunch of text in my console. Here's some of the stuff it says:
nw_socket_set_common_sockopts setsockopt SO_NOAPNFALLBK failed: [42] Protocol not available, dumping backtrace:
nw_connection_endpoint_report [1 192.168.9.67:80 in_progress socket-flow (satisfied)] reported event flow:start_connect
I searched for my print statement which meant success, but could not find it. I could not find any of my other print statements either.
EDIT: The text in my console window comes from me installing SISocket to my project via Cocoapods.
My "result" returns this:
expression produced error: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x8).
The process has been returned to the state before expression evaluation.