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.
Related
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.
I'm testing out the Kentico Cloud Swift SDK to return some 'article' content types (I have created two of them and they are published).
I am using the Boilerplate code as described here:
The result I get is : [Kentico Cloud] Getting items action has succeeded. Received nil items.
My code:
let client = DeliveryClient.init(projectId: <project id>, previewApiKey: <preview key>, secureApiKey: <secure key>, enableDebugLogging: true)
func getArticles(){
// Note: Using "items" as custom query returns all content items,
// but to map them to a single model, a filter is needed.
let customQuery = "items?system.type=article"
// More about strongly-typed models https://github.com/Kentico/cloud-sdk-swift#using-strongly-typed-models
client.getItems(modelType: Article.self, customQuery: customQuery) { (isSuccess, itemsResponse, error) in
if isSuccess {
// We get here and itemsResponse != nil but items == nil
if let articles = itemsResponse?.items {
for article in articles {
}
}
} else {
if let error = error {
print(error)
}
}
}
}
I believe this error message would appear before ObjectMapper is triggered to convert the JSON into Article objects. I could be wrong though.
Anyone have any ideas?
UPDATE
Interestingly, if I request a single article object like so ...
client.getItem(modelType: Article.self, itemName: <codename>) { (isSuccess, itemResponse, error) in
if isSuccess {
if let article = itemResponse?.item {
// Use your item here
}
} else {
if let error = error {
print(error)
}
}
}
... then it works. I get the Article object. It's just asking for all of the articles that fails.
I'm going to investigate the issue later today, however, from your description, it might be caused by the Delivery API item readiness delay - the project was not fully synced with the delivery API yet. After the publishing/unpublishing item or creating/generating the project, there might be a small delay in processing messages by Delivery API which could cause unavailability of item. This delay might be variable - from my experience, it may vary from a couple of seconds to 2-3 minutes. Nevertheless, I'm going to check it just to be sure. I'll keep you updated.
Edit: I'm pretty sure the project was not synced and processed on the Delivery API at the time you were requested the items. The API returned 200, which caused isSuccess in the callback to be true, however, there might have been none or just a subset of items available - I've reproduced this behavior (screenshot below), although it's by design (the content/messages in Event Hub must be processed asynchronously).
I've also suggested the improvement for Kentico Cloud's documentation to mention/explain the possible delay caused by processing events queue messages from Event Hubs.
Just to be sure - could you try it again with your getArticles custom query?
Edit2: Back to your question about the ObjectMapper. This is not an error just a debug message, however, there shouldn't be probably nil but 0 (zero) in the debug message. This message came from:
private func sendGetItemsRequest<T>(url: String, completionHandler: #escaping (Bool, ItemsResponse<T>?, Error?) -> ()) where T: Mappable {
sessionManager.request(url, headers: self.headers).responseObject { (response: DataResponse<ItemsResponse<T>>) in
switch response.result {
case .success:
if let value = response.result.value {
let deliveryItems = value
if self.isDebugLoggingEnabled {
print("[Kentico Cloud] Getting items action has succeeded. Received \(String(describing: deliveryItems.items?.count)) items.")
}
completionHandler(true, deliveryItems, nil)
}
case .failure(let error):
if self.isDebugLoggingEnabled {
print("[Kentico Cloud] Getting items action has failed. Check requested URL: \(url)")
}
completionHandler(false, nil, error)
}
}
}
Ok. This is very weird. After checking the API by requesting an individual item (see the update in the post above), and getting a result (woot). It now seems the original code (unchanged) now works.
I'm wondering if it takes a while for the data to propagate and be available in the API?
Who knows. Weird.
I'm creating a login page, and I used to handle API error such as:
{"status":"error","answer":"Password is wrong"}
this way:
if error?["status"] as? String == "error" {
Alert.showBasic(title: error!["status"] as! String, message: error!["answer"] as! String, vc: self)
This worked perfectly. But now I'm working with another API, that responds to my request this way:
{"password":["password is wrong"]}
The confusion what I have, is that I don't get from API answer, a status for my request, like success or error.
Is there a way to handle it?
The error != nil depends and what you feed to your model and how you feed depends on status code
if let httpResponse = response as? HTTPURLResponse{
switch httpResponse.statusCode {
case 200 : //handle Root data model
case 401 : //handle wrong password model
}
}
URLSession error is different . Its when the request is failed and no response . But you got the response but its up to you differentiate whether its good or bad.
Server response should be static if there is a change not noticed to you means something wrong in the backend.
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