Générez votre propre code d'erreur dans swift 3

88

Ce que j'essaie de réaliser, c'est d'exécuter une URLSessionrequête dans Swift 3. J'effectue cette action dans une fonction distincte (afin de ne pas écrire le code séparément pour GET et POST) et je renvoie URLSessionDataTasket gère le succès et l'échec des fermetures. Un peu comme ça-

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!)
               }
          }
     }
}

Je ne souhaite pas gérer la condition d'erreur dans cette fonction et souhaite générer une erreur en utilisant le code de réponse et renvoyer cette erreur pour la gérer partout où cette fonction est appelée. Quelqu'un peut-il me dire comment procéder? Ou n'est-ce pas la manière «Swift» de gérer de telles situations?

Rikh
la source
Essayez d'utiliser NSErrorau lieu de Errordans la déclaration ( var errorTemp = NSError(...))
Luca D'Alberti
Cela résout le problème, mais je pensais que Swift 3 ne souhaitait pas continuer à utiliser NS?
Rikh
C'est le cas dans le développement iOS. Pour un développement Swift pur, vous devez créer votre propre instance d'erreur en conformant le Errorprotocole
Luca D'Alberti
@ LucaD'Alberti Eh bien, votre solution a résolu le problème, n'hésitez pas à l'ajouter comme réponse pour que je puisse l'accepter!
Rikh

Réponses:

72

Vous pouvez créer un protocole, conforme au LocalizedErrorprotocole Swift , avec ces valeurs:

protocol OurErrorProtocol: LocalizedError {

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

Cela nous permet alors de créer des erreurs concrètes comme ceci:

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
    }
}
Harry Bloom
la source
3
a) il n'est pas nécessaire de créer OurErrorProtocol, demandez simplement à CustomError d'implémenter l'erreur directement. b) cela ne fonctionne pas (au moins dans Swift 3: localizedDescription n'est jamais appelé et vous obtenez "L'opération n'a pas pu être terminée."). Vous devez à la place implémenter LocalizedError; voir ma réponse.
prewett
@prewett Je viens de le remarquer mais tu as raison! L'implémentation du champ errorDescription dans LocalizedError définit en fait le message plutôt que d'utiliser ma méthode comme décrit ci-dessus. Je garde toujours le wrapper "OurErrorProtocol", car j'ai également besoin du champ localizedTitle. Merci d'avoir fait remarquer cela!
Harry Bloom
106

Dans votre cas, l'erreur est que vous essayez de générer une Errorinstance. Errordans Swift 3 est un protocole qui peut être utilisé pour définir une erreur personnalisée. Cette fonctionnalité est spécialement conçue pour les applications Swift pures à exécuter sur différents systèmes d'exploitation.

Dans le développement iOS, la NSErrorclasse est toujours disponible et elle est conforme au Errorprotocole.

Donc, si votre but est uniquement de propager ce code d'erreur, vous pouvez facilement remplacer

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

avec

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

Sinon vérifier le Sandeep Bhandari de » réponse sur la façon de créer un type d'erreur personnalisé

Luca D'Alberti
la source
15
Je viens d' obtenir l'erreur: Error cannot be created because it has no accessible initializers.
Supertecnoboff
@AbhishekThapliyal pourriez-vous s'il vous plaît élaborer un peu plus votre commentaire? Je ne comprends pas ce que tu veux dire.
Luca D'Alberti
2
@ LucaD'Alberti comme dans Swift 4, son erreur d'affichage ne peut pas être créée car il n'a pas d'initialiseurs accessibles, lors de la création d'un objet d'erreur.
Maheep
1
@Maheep ce que je suggère dans ma réponse n'est pas d'utiliser Error, mais NSError. Bien sûr, l'utilisation Errorgénère une erreur.
Luca D'Alberti
L'erreur est le protocole. Ne peut pas être instancié directement.
slobodans
52

Vous pouvez créer des énumérations pour traiter les erreurs :)

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

puis créez une méthode à l'intérieur de enum pour recevoir le code de réponse http et renvoyer l'erreur correspondante en retour :)

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
        }
    }

Enfin, mettez à jour votre bloc d'échec pour accepter un seul paramètre de type RikhError :)

J'ai un tutoriel détaillé sur la façon de restructurer le modèle de réseau orienté objet traditionnel basé sur Objective-C en un modèle moderne orienté protocole en utilisant Swift3 ici https://learnwithmehere.blogspot.in Jetez un oeil :)

J'espère que cela aide :)

Sandeep Bhandari
la source
Ahh mais cela ne devra-t-il pas me faire gérer manuellement tous les cas? C'est taper les codes d'erreur?
Rikh
Oui, vous devez: D Mais en même temps, vous pouvez effectuer diverses actions spécifiques à chaque état d'erreur :) maintenant vous avez un contrôle fin sur le modèle d'erreur si au cas où vous ne voulez pas le faire, vous pouvez utiliser le cas 400 ... 404 {...} gérer uniquement les cas génériques :)
Sandeep Bhandari
Ahh ouais! Merci
Rikh
En supposant que plusieurs codes http n'ont pas besoin de pointer vers le même cas, vous devriez pouvoir simplement faire enum RikhError: Int, Error {case invalidRequest = 400}, puis le créer RikhError (rawValue: httpCode)
Brian F Leighty
48

Vous devez utiliser l'objet NSError.

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

Puis cast NSError en objet Error

Ahmed Lotfy
la source
27

Détails

  • Version 10.2.1 de Xcode (10E1001)
  • Swift 5

Solution des erreurs d'organisation dans une application

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)
}
Vasily Bodnarchuk
la source
16

Implémentez 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
    }
}

Notez que simplement implémenter Error, par exemple, comme décrit dans l'une des réponses, échouera (au moins dans Swift 3) et que l'appel de localizedDescription entraînera la chaîne "L'opération n'a pas pu être terminée. (.StringError error 1.) "

Prewett
la source
Devrait-il être mMsg = msg
Brett
1
Oups, c'est vrai. J'ai changé "msg" en "description", ce qui, espérons-le, est un peu plus clair que mon original.
prewett
4
Vous pouvez réduire cela à struct StringError : LocalizedError { public let errorDescription: String? }, et utiliser simplement commeStringError(errorDescription: "some message")
Koen.
7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

créer un objet NSError et le convertir en erreur, l'afficher n'importe où

private func showLoginError(_ error: Error?) {
    if let errorObj = error {
        UIAlertController.alert("Login Error", message: errorObj.localizedDescription).action("OK").presentOn(self)
    }
}
Suraj K Thomas
la source
4

Je pense toujours que la réponse de Harry est la plus simple et la plus complète, mais si vous avez besoin de quelque chose d'encore plus simple, utilisez:

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 }
}

Et utilisez ou testez-le comme ceci:

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

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}
Reimond Hill
la source
2
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)
}
Daniel.scheibe
la source
1

Je sais que vous êtes déjà satisfait d'une réponse, mais si vous souhaitez connaître la bonne approche, cela pourrait vous être utile. Je préférerais ne pas mélanger le code d'erreur de réponse http avec le code d'erreur dans l'objet d'erreur (confus? Veuillez continuer à lire un peu ...).

Les codes de réponse http sont des codes d'erreur standard concernant une réponse http définissant des situations génériques lorsque la réponse est reçue et varie de 1xx à 5xx (par exemple 200 OK, 408 Request timed out, 504 Gateway timeout etc - http://www.restapitutorial.com/ httpstatuscodes.html )

Le code d'erreur dans un objet NSError fournit une identification très spécifique du type d'erreur décrit par l'objet pour un domaine particulier d'application / produit / logiciel. Par exemple, votre application peut utiliser 1000 pour "Désolé, vous ne pouvez pas mettre à jour cet enregistrement plus d'une fois par jour" ou dire 1001 pour "Vous avez besoin du rôle de gestionnaire pour accéder à cette ressource" ... qui sont spécifiques à votre domaine / application logique.

Pour une toute petite application, ces deux concepts sont parfois fusionnés. Mais ils sont complètement différents comme vous pouvez le voir et très importants et utiles pour concevoir et travailler avec de gros logiciels.

Donc, il peut y avoir deux techniques pour mieux gérer le code:

1. Le rappel de fin exécutera toutes les vérifications

completionHandler(data, httpResponse, responseError) 

2. Votre méthode décide du succès et de la situation d'erreur, puis appelle le rappel correspondant

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

Bon codage :)

Tushar
la source
Donc, fondamentalement, ce que vous essayez de dire, c'est de passer le paramètre "data" au cas où il y aurait une chaîne spécifique à afficher en cas d'un code d'erreur spécifique renvoyé par le serveur? (Désolé, je peux parfois être un peu lent!)
Rikh