URLsession .failure crash the app because error is nil - swift

I am trying to throw an error when I have status code 500. when I hit this line completion(.failure(error!)) I have "Fatal error: Unexpectedly found nil while unwrapping an Optional value" is they are a problem with my URL session functional?
func PutRoleLedgerTransaction_DebitDistributorBalance(...,completion: #escaping (Result<Data, Error>) -> Void){
let jsonData = role_ledger_object.data(using: .utf8)
let componentURL = createURLComponents(path: "")
print(componentURL.url!)
guard let validURL = componentURL.url else {
print("URL creation failed...")
return
}
var request = URLRequest(url:validURL)
request.setValue("application/json", forHTTPHeaderField: "Content-Type" )
request.setValue("application/json", forHTTPHeaderField: "Accept" )
request.httpMethod = "PUT"
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
print("PutRoleLedgerTransaction API status: \(httpResponse.statusCode)")
let message: String = HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode)
print("httpResponse.allHeaderFields \(message)")
if httpResponse.statusCode > 300{
completion(.failure(error!))
return
}
}
guard let validData = data, error == nil else {
completion(.failure(error!))
return
}
do {
completion(.success(validData))
} catch let serializationError {
completion(.failure(serializationError))
}
}.resume()
}

A server 500 response is necessarily not equal to an URLSession error, you must not unwrap the optional carelessly.
Create a custom error
enum HTTPResponseError : Error {
case serverFailed(String)
}
In the closure first handle the URLSession error, then the response and return the message if the status code is not 200
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
if let httpResponse = response as? HTTPURLResponse {
print("PutRoleLedgerTransaction API status: \(httpResponse.statusCode)")
let message: String = HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode)
print("httpResponse.allHeaderFields \(message)")
if httpResponse.statusCode != 200 {
completion(.failure(HTTPResponseError.serverFailed(message)))
return
}
}
// The do block makes no sense if no error is being thrown
// do {
// force unwrapping data is safe if error is nil.
completion(.success(data!))
// } catch {
// completion(.failure(error))
// }
...

Related

Handle URLSession timeout

I am use URLSession for POST request and obtain list of players, but sometimes I am obtain error "Error Domain=NSURLErrorDomain Code=-1001", how I can handle it?
How I am get error inside "completion"?
func addPlayer(playerCode: String, completion: #escaping (Error?) -> ()) {
guard let url = URL(string: "\(url)") else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
let playerCode = [playerCode]
let params = ["players": playerCode]
do {
let data = try JSONSerialization.data(withJSONObject: params, options: .init())
urlRequest.httpBody = data
urlRequest.setValue("application/json", forHTTPHeaderField: "content-type")
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 5.0
DispatchQueue.global(qos: .background).async {
URLSession(configuration: sessionConfig).dataTask(with: urlRequest) { (data, resp, err) in
guard let data = data else { return }
guard let resp = resp else { return }
print(data)
print(resp)
guard let response = resp as? HTTPURLResponse, (200 ..< 530) ~= response.statusCode else {
print("Error: HTTP request failed")
return
}
if response.statusCode == 200 {
print("Status code is 200")
do {
let json = try JSONDecoder().decode(ListOfPlayers.self, from: data)
// I am do what I am want
} catch let jsonError {
print("Error json serialization \(jsonError)")
}
completion(nil)
} else if response.statusCode == 422 {
print("Status code is 422")
completion(nil)
return
}
}.resume()
}
} catch {
}
}
Also I am try use this code:
guard let err = err else { return }
if (err._code == -1001) {
print("Oops bad connection")
}
but this code don't executed during "Time out"
You can cast your error to URLError type and then use the code property to safely check if it's a session timeout error:
if (err as? URLError)?.code == .timedOut {
// Handle session timeout
}

Passing JSON Decode error into something actionable

I'm trying to improve my error handling when I don't get back any data in my JSON. The function below works fine without any issues except if I catch an error. I'm not sure of how to pass the error to prompt the user or is the way to handle the error if googlebusinessinfo is nil I likely have no data and pass a generic error?
Here's my function:
func getBusinessReviews(googleUrl: String, completion: #escaping (WelcomeReview?) -> ()) {
// Create URL
let url = URL(string: googleUrl)
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
//Set HTTP Header
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Specify HTTP Method to use
request.httpMethod = "GET"
print("(Google)Do we have any data: \(String(describing: data))")
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("(Google)Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Googledata Response data string:\n \(dataString)")
}
if let googlebusinessdata = data {
do {
let googlebusinessinfo = try JSONDecoder().decode(WelcomeReview.self, from: googlebusinessdata)
print("googledata \(googlebusinessinfo)")
completion(googlebusinessinfo)
} catch
{
print("googledata error \(error.localizedDescription)")
let googebusinessinfoerror = error.localizedDescription
}
}
}
task.resume()
}
First of all you can improve the error handling immensely if you print error instead of error.localizedDescription in a Decoding catch block. The former shows the real error, the latter a meaningless generic message.
To answer your question use the generic Result type
func getBusinessReviews(googleUrl: String, completion: #escaping (Result<WelcomeReview,Error>) -> ()) {
// Create URL
let url = URL(string: googleUrl)
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
//Set HTTP Header
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Specify HTTP Method to use
request.httpMethod = "GET"
print("(Google)Do we have any data: \(String(describing: data))")
// Check if Error took place
if let error = error {
print("Error took place \(error)")
completion(.failure(error))
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("(Google)Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Googledata Response data string:\n \(dataString)")
do {
let googlebusinessinfo = try JSONDecoder().decode(WelcomeReview.self, from: data)
print("googledata \(googlebusinessinfo)")
completion(.success(googlebusinessinfo))
} catch
{
print("googledata error:", error)
let googebusinessinfoerror = error.localizedDescription
completion(.failure(error))
}
}
}
task.resume()
}
and call it
getBusinessReviews(googleUrl: "Foo") { result in
switch result {
case .success(let info): print(info)
case .failure(let error): print(error)
}
}

Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)

Below line of code is producing the error,
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
When print jsonData This code returns perfect result of array but getting this error,
Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)
As the error says I understand its a cast exception, but I'm not able to modify the code to make it work. I'm kinda new to Swift, so any help would be appreciated. Below is my
import Foundation
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<Any, Error>, Error) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error), Error.self as! Error)
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response")
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data")
return
}
do {
// Parse the data
let decoder = JSONDecoder()
let jsonData = try decoder.decode(APIService.self, from: data)
// print(jsonData)
// Back to the main thread
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
} catch let error {
completion(.failure(error),error)
}
}
dataTask?.resume()
}
}
Modify the completion block parameters, you already are returning the error inside the Result's .failure(Error) block so no need to repeat it again as another parameter in the completion parameter. Here's how you fix this:
Declaration:
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<CategoriesNewsData, Error>) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error))
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response") // Throw a custom error here too.
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data") // Throw a custom error here too.
return
}
do {
let decoder = JSONDecoder()
let jsonData = try decoder.decode(CategoriesNewsData.self, from: data)
DispatchQueue.main.async {
completion(.success(jsonData))
}
} catch let error {
completion(.failure(error))
}
}
dataTask?.resume()
}
}
Calling:
service.getPopularPosts { result in
switch result {
case .success(let categoriesNewsData):
print(categoriesNewsData)
case .failure(let error):
print(error)
}
}

function with dataTask returning a value

I wan't to check if my url statusCode equals to 200, I created a function returning a Boolean if the statusCode equals to 200, I'm using a dataTask, but I don't know how to return a value:
class func checkUrl(urlString: String) -> Bool{
let urlPath: String = urlString
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(url: url as URL)
var response: URLResponse?
let session = Foundation.URLSession.shared
var task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if httpResponse.statusCode == 200{
return true
} else {
return false
}
}
})
task.resume()
}
The returns in if else are returning an error:
Unexpected non-void return value in void function
in order to return value you should use blocks. Try declaring your function like this:
class func checkUrl(urlString: String, finished: ((isSuccess: Bool)->Void) {
let urlPath: String = urlString
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(url: url as URL)
var response: URLResponse?
let session = Foundation.URLSession.shared
var task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if httpResponse.statusCode == 200{
finished(isSuccess: true)
} else {
finished(isSuccess: false)
}
}
})
task.resume()
}
And then call it like this:
checkUrl("http://myBestURL.com", finished { isSuccess in
// Handle logic after return here
})
Hope that this will help.
Consider semaphore if you want to keep your original return pattern.
func checkUrl(urlString: String) -> Bool {
if let url = URL(string: fileUrl) {
var result: Bool!
let semaphore = DispatchSemaphore(value: 0) //1. create a counting semaphore
let session = URLSession.shared
session.dataTask(with: url, completionHandler: { (data, response, error) in
result = true //or false in case
semaphore.signal() //3. count it up
}).resume()
semaphore.wait() //2. wait for finished counting
return result
}
return false
}
Swift4, work in my case
Try to add guard let data = data else { return } in dataTask like:
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
print("get some data")
}.resume()
You're returning a value from a Void function that is the completionHandler closure of dataTask(_:, _:)
Regarding your code, there is something wrong: you can't return that value because it's executed on a different thread, it's an asynchronous operation. Please take a look at this thread: Returning data from async call in Swift function

Cast from 'NSData' to unrelated type 'NSHTTPURLResponse' always fails

I have this code :
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
guard let response = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error")
print(error)
return
}
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("OK!!!")
}
})
task.resume()
At the line : let httpResponse = response as! NSHTTPURLResponse I have this strange warning : Cast from 'NSData' to unrelated type 'NSHTTPURLResponse' always fails
How should I get rid of it ? Thanks!
You are unwrapping the content of data in a constant called response. It's the source of your issue, because then you are using one thinking you are using the other.
Also, why force unwrapping the downcast?
It's better to continue using guard like you already do.
Example:
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
guard let data = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error")
print(error)
return
}
guard let httpResponse = response as? NSHTTPURLResponse else {
print("response unavailable")
return
}
let statusCode = httpResponse.statusCode
if statusCode == 200 {
print("OK!!!")
}
})
task.resume()