Generieren Sie in Swift 3 Ihren eigenen Fehlercode

83

Ich versuche, eine URLSessionAnforderung in Swift 3 auszuführen . Ich führe diese Aktion in einer separaten Funktion aus (um den Code für GET und POST nicht separat zu schreiben) URLSessionDataTaskund gebe den Erfolg und Misserfolg bei Abschlüssen zurück und behandle ihn. So ähnlich wie-

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

Ich möchte die Fehlerbedingung in dieser Funktion nicht behandeln und möchte einen Fehler unter Verwendung des Antwortcodes erzeugen und diesen Fehler zurückgeben, um ihn zu behandeln, wo immer diese Funktion aufgerufen wird. Kann mir jemand sagen, wie ich das anstellen soll? Oder ist dies nicht der "schnelle" Weg, um mit solchen Situationen umzugehen?

Rikh
quelle
Versuchen Sie es NSErroranstelle von Errorin der Deklaration ( var errorTemp = NSError(...))
Luca D'Alberti
Das löst das Problem, aber ich dachte, Swift 3 möchte NS nicht weiter verwenden?
Rikh
Dies ist in der iOS-Entwicklung der Fall. Für eine reine Swift-Entwicklung sollten Sie Ihre eigene Fehlerinstanz erstellen, indem Sie das ErrorProtokoll anpassen
Luca D'Alberti,
@ LucaD'Alberti Nun, Ihre Lösung hat das Problem gelöst. Sie können es gerne als Antwort hinzufügen, damit ich es akzeptieren kann!
Rikh

Antworten:

66

Sie können ein Protokoll erstellen, das dem Swift- LocalizedErrorProtokoll entspricht, mit folgenden Werten:

protocol OurErrorProtocol: LocalizedError {

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

Dies ermöglicht es uns dann, konkrete Fehler wie folgt zu erzeugen:

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
quelle
3
a) Es ist nicht erforderlich, OurErrorProtocol zu erstellen. Lassen Sie CustomError Error nur direkt implementieren. b) Dies funktioniert nicht (zumindest in Swift 3: localizedDescription wird nie aufgerufen und Sie erhalten "Der Vorgang konnte nicht abgeschlossen werden."). Sie müssen stattdessen LocalizedError implementieren. siehe meine Antwort.
Prewett
@prewett Ich habe es gerade bemerkt, aber du hast recht! Durch die Implementierung des Felds errorDescription in LocalizedError wird die Nachricht tatsächlich festgelegt, anstatt meine Methode wie oben beschrieben zu verwenden. Ich behalte jedoch weiterhin den Wrapper "OurErrorProtocol", da ich auch das Feld localizedTitle benötige. Vielen Dank für den Hinweis!
Harry Bloom
102

In Ihrem Fall besteht der Fehler darin, dass Sie versuchen, eine ErrorInstanz zu generieren . Errorin Swift 3 ist ein Protokoll, mit dem ein benutzerdefinierter Fehler definiert werden kann. Diese Funktion ist speziell für reine Swift-Anwendungen vorgesehen, die unter verschiedenen Betriebssystemen ausgeführt werden.

In der iOS-Entwicklung ist die NSErrorKlasse weiterhin verfügbar und entspricht dem ErrorProtokoll.

Wenn Sie diesen Fehlercode nur weitergeben möchten, können Sie ihn problemlos ersetzen

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

mit

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

Überprüfen Sie andernfalls die Antwort von Sandeep Bhandari , wie Sie einen benutzerdefinierten Fehlertyp erstellen

Luca D'Alberti
quelle
15
Ich bekomme nur den Fehler : Error cannot be created because it has no accessible initializers.
Supertecnoboff
@AbhishekThapliyal Könnten Sie bitte etwas mehr Ihren Kommentar ausarbeiten? Ich kann nicht verstehen, was du meinst.
Luca D'Alberti
2
@ LucaD'Alberti wie in Swift 4 kann der angezeigte Fehler nicht erstellt werden, da beim Erstellen des Fehlerobjekts keine zugänglichen Initialisierer vorhanden sind.
Maheep
1
@ Maheep was ich in meiner Antwort vorschlage ist nicht zu verwenden Error, aber NSError. Natürlich Errorwirft die Verwendung einen Fehler.
Luca D'Alberti
Fehler ist das Protokoll. Kann nicht direkt instanziiert werden.
Slobodans
51

Sie können Aufzählungen erstellen, um mit Fehlern umzugehen :)

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

und erstellen Sie dann eine Methode in enum, um den http-Antwortcode zu erhalten und den entsprechenden Fehler zurückzugeben :)

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

Aktualisieren Sie schließlich Ihren Fehlerblock, um einzelne Parameter vom Typ RikhError zu akzeptieren :)

Ich habe eine detaillierte Anleitung, wie die Umstrukturierung der traditionellen Objective - C auf Basis der objektorientierten Netzwerk - Modell der modernen Protokoll Modell Oriented mit swift3 hier https://learnwithmehere.blogspot.in Werfen Sie einen Blick :)

Ich hoffe es hilft :)

Sandeep Bhandari
quelle
Ahh, aber muss ich dann nicht alle Fälle manuell bearbeiten? Das ist die Fehlercodes eingeben?
Rikh
Ja, Sie müssen: D Aber gleichzeitig können Sie verschiedene Aktionen ausführen, die für jeden Fehlerstatus spezifisch sind :) Jetzt haben Sie eine Feinsteuerung des Fehlermodells, wenn Sie dies nicht möchten, können Sie den Fall 400 ... 404 verwenden {...} behandeln nur generische Fälle :)
Sandeep Bhandari
Ahh yep! Vielen Dank
Rikh
Angenommen, mehrere http-Codes müssen nicht auf denselben Fall verweisen, sollten Sie in der Lage sein, RikhError: Int, Error {case invalidRequest = 400} aufzulisten und dann RikhError (rawValue: httpCode) zu erstellen
Brian F Leighty
45

Sie sollten das NSError-Objekt verwenden.

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

Wandeln Sie dann NSError in das Fehlerobjekt um

Ahmed Lotfy
quelle
26

Einzelheiten

  • Xcode Version 10.2.1 (10E1001)
  • Swift 5

Lösung zum Organisieren von Fehlern in einer 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
        }
    }
}

Verwendung

//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)
}
Wassili Bodnarchuk
quelle
16

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

Beachten Sie, dass das einfache Implementieren von beispielsweise Fehler, wie in einer der Antworten beschrieben, fehlschlägt (zumindest in Swift 3) und das Aufrufen von localizedDescription zu der Zeichenfolge "Der Vorgang konnte nicht abgeschlossen werden. (.StringError error 1.) führt. ""

vorbenetzen
quelle
Sollte das mMsg = msg sein
Brett
1
Ups, richtig. Ich habe "msg" in "description" geändert, was hoffentlich etwas klarer ist als mein Original.
Prewett
4
Sie können das auf reduzieren struct StringError : LocalizedError { public let errorDescription: String? }und das einfach alsStringError(errorDescription: "some message")
Koen verwenden.
7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

Erstellen Sie ein NSError-Objekt, typisieren Sie es in Error und zeigen Sie es an einer beliebigen Stelle an

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

Ich denke immer noch, dass Harrys Antwort die einfachste und vollständigste ist, aber wenn Sie etwas noch Einfacheres brauchen, dann verwenden Sie:

struct AppError: Error {
    private let message: String

    var localizedDescription: String {
        return message
    }
    
    init(message: String) {
        self.message = message
    }
}

Und benutze es so:

let appError = AppError(message: "AppError")
print(appError.localizedDescription)
Reimond Hill
quelle
1

Ich weiß, dass Sie mit einer Antwort bereits zufrieden sind, aber wenn Sie daran interessiert sind, den richtigen Ansatz zu kennen, kann dies für Sie hilfreich sein. Ich würde es vorziehen, den http-Antwort-Fehlercode nicht mit dem Fehlercode im Fehlerobjekt zu mischen (verwirrt? Bitte lesen Sie weiter ...).

Die http-Antwortcodes sind Standardfehlercodes für eine http-Antwort, die allgemeine Situationen definieren, in denen eine Antwort empfangen wird. Sie variieren von 1xx bis 5xx (z. B. 200 OK, 408 Zeitüberschreitung bei Anforderungen, 504 Gateway-Zeitüberschreitung usw. - http://www.restapitutorial.com/). httpstatuscodes.html )

Der Fehlercode in einem NSError-Objekt bietet eine sehr spezifische Identifikation für die Art von Fehler, die das Objekt für eine bestimmte Domäne von Anwendung / Produkt / Software beschreibt. Beispielsweise verwendet Ihre Anwendung möglicherweise 1000 für "Entschuldigung, Sie können diesen Datensatz nicht mehr als einmal am Tag aktualisieren" oder 1001 für "Sie benötigen eine Manager-Rolle, um auf diese Ressource zuzugreifen" ... die für Ihre Domain / Anwendung spezifisch sind Logik.

Bei einer sehr kleinen Anwendung werden diese beiden Konzepte manchmal zusammengeführt. Aber sie sind völlig anders, wie Sie sehen können, und sehr wichtig und hilfreich beim Entwerfen und Arbeiten mit großer Software.

Es gibt also zwei Techniken, um den Code besser zu handhaben:

1. Der Abschlussrückruf führt alle Überprüfungen durch

completionHandler(data, httpResponse, responseError) 

2. Ihre Methode entscheidet über die Erfolgs- und Fehlersituation und ruft dann den entsprechenden Rückruf auf

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

Viel Spaß beim Codieren :)

Tushar
quelle
Sie versuchen also im Grunde, den Parameter "data" zu übergeben, falls eine bestimmte Zeichenfolge angezeigt werden soll, falls ein bestimmter Fehlercode vom Server zurückgegeben wird. (Entschuldigung, ich kann manchmal etwas langsam sein!)
Rikh
1
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
quelle