124

What I am trying to achieve is perform a URLSession request in swift 3. I am performing this action in a separate function (so as not to write the code separately for GET and POST) and returning the URLSessionDataTask and handling the success and failure in closures. Sort of like this-

let task = URLSession.shared.dataTask(with: request) { (data, uRLResponse, responseError) in

     DispatchQueue.main.async {

          var httpResponse = uRLResponse as! HTTPURLResponse

          if responseError != nil && httpResponse.statusCode == 200{

               successHandler(data!)

          }else{

               if(responseError == nil){
                     //Trying to achieve something like below 2 lines
                     //Following line throws an error soo its not possible
                     //var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

                     //failureHandler(errorTemp)

               }else{

                     failureHandler(responseError!)
               }
          }
     }
}

I do not wish to handle the error condition in this function and wish to generate an error using the response code and return this Error to handle it wherever this function is called from. Can anybody tell me how to go about this? Or is this not the "Swift" way to go about handling such situations?

4
  • Try using NSError instead of Error in the declaration (var errorTemp = NSError(...)) Commented Nov 18, 2016 at 8:05
  • That solves the problem but I thought swift 3 doesn't wish to continue with using NS?
    – Rikh
    Commented Nov 18, 2016 at 8:07
  • It does in iOS development. For pure Swift development you should create your own error instance by conforming the Error protocol Commented Nov 18, 2016 at 8:08
  • @LucaD'Alberti Well your solution did solve the problem, feel free to add it as an answer so that i can accept it!
    – Rikh
    Commented Nov 18, 2016 at 8:13

10 Answers 10

126

In your case, the error is that you're trying to generate an Error instance. Error in Swift 3 is a protocol that can be used to define a custom error. This feature is especially for pure Swift applications to run on different OS.

In iOS development the NSError class is still available and it conforms to Error protocol.

So, if your purpose is only to propagate this error code, you can easily replace

var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

with

var errorTemp = NSError(domain:"", code:httpResponse.statusCode, userInfo:nil)

Otherwise check the Sandeep Bhandari's answer regarding how to create a custom error type

5
  • 28
    I just get the error: Error cannot be created because it has no accessible initializers. Commented Mar 30, 2018 at 10:29
  • @AbhishekThapliyal could you please elaborate a bit more your comment? I can't understand what you mean. Commented Apr 26, 2018 at 6:55
  • 2
    @LucaD'Alberti as in Swift 4 its showing Error cannot be created because it has no accessible initializers, while creating Error Object.
    – Maheep
    Commented Nov 21, 2018 at 15:36
  • 2
    @Maheep what I'm suggesting in my answer is not to use Error, but NSError. Of course using Error throws an error. Commented Nov 22, 2018 at 13:15
  • Error is the protocol. Cant be instantiated directly.
    – slobodans
    Commented Mar 12, 2020 at 10:08
88

You can create a protocol, conforming to the Swift LocalizedError protocol, with these values:

protocol OurErrorProtocol: LocalizedError {

    var title: String? { get }
    var code: Int { get }
}

This then enables us to create concrete errors like so:

struct CustomError: OurErrorProtocol {

    var title: String?
    var code: Int
    var errorDescription: String? { return _description }
    var failureReason: String? { return _description }

    private var _description: String

    init(title: String?, description: String, code: Int) {
        self.title = title ?? "Error"
        self._description = description
        self.code = code
    }
}
2
  • 3
    a) it isn't necessary to create OurErrorProtocol, just have CustomError implement Error directly. b) this doesn't work (at least in Swift 3: localizedDescription is never called and you get "The operation couldn't be completed."). You need to implement LocalizedError instead; see my answer.
    – prewett
    Commented Jun 30, 2017 at 20:12
  • @prewett I just noticed but you are right! Implementing the errorDescription field in LocalizedError in fact sets the message rather than using my method as described above. I'm still keeping the "OurErrorProtocol" wrapper though, as I need the localizedTitle field as well. Thanks for pointing that out! Commented Sep 18, 2017 at 13:27
79

You should use NSError object.

let error = NSError(domain: "", code: 401, userInfo: [ NSLocalizedDescriptionKey: "Invalid access token"])

Then cast NSError to Error object.

63

You can create enums to deal with errors :)

enum RikhError: Error {
    case unknownError
    case connectionError
    case invalidCredentials
    case invalidRequest
    case notFound
    case invalidResponse
    case serverError
    case serverUnavailable
    case timeOut
    case unsuppotedURL
 }

and then create a method inside enum to receive the http response code and return the corresponding error in return :)

static func checkErrorCode(_ errorCode: Int) -> RikhError {
        switch errorCode {
        case 400:
            return .invalidRequest
        case 401:
            return .invalidCredentials
        case 404:
            return .notFound
        //bla bla bla
        default:
            return .unknownError
        }
    }

Finally update your failure block to accept single parameter of type RikhError :)

I have a detailed tutorial on how to restructure traditional Objective - C based Object Oriented network model to modern Protocol Oriented model using Swift3 here https://learnwithmehere.blogspot.in Have a look :)

Hope it helps :)

3
  • Ahh but won't this have to make me manually handle all the cases? That is type the error codes?
    – Rikh
    Commented Nov 18, 2016 at 8:10
  • Yup you have to :D But at the same time you can take various actions specific to each error status :) now you have a fine control on error model if in case you dont want to do it you can use case 400 ... 404 {... } handle just generic cases :) Commented Nov 18, 2016 at 8:13
  • Assuming multiple http codes don't need to point to the same case you should be able to just do enum RikhError: Int, Error { case invalidRequest = 400 } and then to create it RikhError(rawValue: httpCode) Commented Dec 6, 2017 at 17:22
41

Details

  • Xcode Version 10.2.1 (10E1001)
  • Swift 5

Solution of organizing errors in an app

import Foundation

enum AppError {
    case network(type: Enums.NetworkError)
    case file(type: Enums.FileError)
    case custom(errorDescription: String?)

    class Enums { }
}

extension AppError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .network(let type): return type.localizedDescription
            case .file(let type): return type.localizedDescription
            case .custom(let errorDescription): return errorDescription
        }
    }
}

// MARK: - Network Errors

extension AppError.Enums {
    enum NetworkError {
        case parsing
        case notFound
        case custom(errorCode: Int?, errorDescription: String?)
    }
}

extension AppError.Enums.NetworkError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .parsing: return "Parsing error"
            case .notFound: return "URL Not Found"
            case .custom(_, let errorDescription): return errorDescription
        }
    }

    var errorCode: Int? {
        switch self {
            case .parsing: return nil
            case .notFound: return 404
            case .custom(let errorCode, _): return errorCode
        }
    }
}

// MARK: - FIle Errors

extension AppError.Enums {
    enum FileError {
        case read(path: String)
        case write(path: String, value: Any)
        case custom(errorDescription: String?)
    }
}

extension AppError.Enums.FileError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .read(let path): return "Could not read file from \"\(path)\""
            case .write(let path, let value): return "Could not write value \"\(value)\" file from \"\(path)\""
            case .custom(let errorDescription): return errorDescription
        }
    }
}

Usage

//let err: Error = NSError(domain:"", code: 401, userInfo: [NSLocalizedDescriptionKey: "Invaild UserName or Password"])
let err: Error = AppError.network(type: .custom(errorCode: 400, errorDescription: "Bad request"))

switch err {
    case is AppError:
        switch err as! AppError {
        case .network(let type): print("Network ERROR: code \(type.errorCode), description: \(type.localizedDescription)")
        case .file(let type):
            switch type {
                case .read: print("FILE Reading ERROR")
                case .write: print("FILE Writing ERROR")
                case .custom: print("FILE ERROR")
            }
        case .custom: print("Custom ERROR")
    }
    default: print(err)
}
18

Implement LocalizedError:

struct StringError : LocalizedError
{
    var errorDescription: String? { return mMsg }
    var failureReason: String? { return mMsg }
    var recoverySuggestion: String? { return "" }
    var helpAnchor: String? { return "" }

    private var mMsg : String

    init(_ description: String)
    {
        mMsg = description
    }
}

Note that simply implementing Error, for instance, as described in one of the answers, will fail (at least in Swift 3), and calling localizedDescription will result in the string "The operation could not be completed. (.StringError error 1.)"

3
  • Should that be mMsg = msg
    – Brett
    Commented Oct 18, 2017 at 6:23
  • 1
    Oops, right. I changed "msg" to "description", which hopefully is a bit clearer than my original.
    – prewett
    Commented Oct 23, 2017 at 22:53
  • 4
    You can reduce that to struct StringError : LocalizedError { public let errorDescription: String? }, and that simply use as StringError(errorDescription: "some message")
    – Koen.
    Commented Feb 24, 2018 at 21:47
13

I still think that Harry's answer is the simplest and completed but if you need something even simpler, then use:

struct AppError {
    let message: String

    init(message: String) {
        self.message = message
    }
}

extension AppError: LocalizedError {
    var errorDescription: String? { return message }
//    var failureReason: String? { get }
//    var recoverySuggestion: String? { get }
//    var helpAnchor: String? { get }
}

And use or test it like this:

printError(error: AppError(message: "My App Error!!!"))

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}
9
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

create an NSError object and typecast it to Error ,show it anywhere

private func showLoginError(_ error: Error?) {
    if let errorObj = error {
        UIAlertController.alert("Login Error", message: errorObj.localizedDescription).action("OK").presentOn(self)
    }
}
5
protocol CustomError : Error {

    var localizedTitle: String
    var localizedDescription: String

}

enum RequestError : Int, CustomError {

    case badRequest         = 400
    case loginFailed        = 401
    case userDisabled       = 403
    case notFound           = 404
    case methodNotAllowed   = 405
    case serverError        = 500
    case noConnection       = -1009
    case timeOutError       = -1001

}

func anything(errorCode: Int) -> CustomError? {

      return RequestError(rawValue: errorCode)
}
2

I know you have already satisfied with an answer but if you are interested to know the right approach, then this might be helpful for you. I would prefer not to mix http-response error code with the error code in the error object (confused? please continue reading a bit...).

The http response codes are standard error codes about a http response defining generic situations when response is received and varies from 1xx to 5xx ( e.g 200 OK, 408 Request timed out,504 Gateway timeout etc - http://www.restapitutorial.com/httpstatuscodes.html )

The error code in a NSError object provides very specific identification to the kind of error the object describes for a particular domain of application/product/software. For example your application may use 1000 for "Sorry, You can't update this record more than once in a day" or say 1001 for "You need manager role to access this resource"... which are specific to your domain/application logic.

For a very small application, sometimes these two concepts are merged. But they are completely different as you can see and very important & helpful to design and work with large software.

So, there can be two techniques to handle the code in better way:

1. The completion callback will perform all the checks

completionHandler(data, httpResponse, responseError) 

2. Your method decides success and error situation and then invokes corresponding callback

if nil == responseError { 
   successCallback(data)
} else {
   failureCallback(data, responseError) // failure can have data also for standard REST request/response APIs
}

Happy coding :)

1
  • So basically what you are trying to say is pass the "data" parameter in case there is some specific string to be displayed in case of a specific error code as returned from the server? (Sorry, I can be a bit slow at times!)
    – Rikh
    Commented Nov 18, 2016 at 15:42

Not the answer you're looking for? Browse other questions tagged or ask your own question.