Genera il tuo codice di errore in swift 3


91

Quello che sto cercando di ottenere è eseguire una URLSessionrichiesta in swift 3. Sto eseguendo questa azione in una funzione separata (in modo da non scrivere il codice separatamente per GET e POST) e restituendo ilURLSessionDataTask e gestendo il successo e il fallimento nelle chiusure. Un po 'come questo-

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

Non desidero gestire la condizione di errore in questa funzione e desidero generare un errore utilizzando il codice di risposta e restituire questo errore per gestirlo ovunque venga chiamata questa funzione. Qualcuno può dirmi come procedere? O non è questo il modo "rapido" di affrontare tali situazioni?


Prova ad usare al NSErrorposto della Errordichiarazione ( var errorTemp = NSError(...))
Luca D'Alberti

Questo risolve il problema ma ho pensato che swift 3 non desidera continuare a utilizzare NS?
Rikh

Lo fa nello sviluppo iOS. Per lo sviluppo puro Swift dovresti creare la tua istanza di errore conformando il Errorprotocollo
Luca D'Alberti

@ LucaD'Alberti Beh, la tua soluzione ha risolto il problema, sentiti libero di aggiungerla come risposta in modo che io possa accettarla!
Rikh

Risposte:


74

Puoi creare un protocollo, conforme al LocalizedErrorprotocollo Swift , con questi valori:

protocol OurErrorProtocol: LocalizedError {

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

Questo ci consente quindi di creare errori concreti come questo:

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

3
a) non è necessario creare OurErrorProtocol, basta fare in modo che CustomError implementi direttamente Error. b) questo non funziona (almeno in Swift 3: localizedDescription non viene mai chiamato e ottieni "Impossibile completare l'operazione."). Devi invece implementare LocalizedError; vedere la mia risposta.
prewett il

@prewett l'ho appena notato ma hai ragione! L'implementazione del campo errorDescription in LocalizedError infatti imposta il messaggio piuttosto che utilizzare il mio metodo come descritto sopra. Tuttavia, conservo ancora il wrapper "OurErrorProtocol", poiché ho bisogno anche del campo localizedTitle. Grazie per la segnalazione!
Harry Bloom

106

Nel tuo caso, l'errore è che stai tentando di generare Errorun'istanza.Errorin Swift 3 è un protocollo che può essere utilizzato per definire un errore personalizzato. Questa funzione è specialmente per le applicazioni Swift pure da eseguire su sistemi operativi diversi.

Nello sviluppo iOS la NSErrorclasse è ancora disponibile e conforme al Errorprotocollo.

Quindi, se il tuo scopo è solo propagare questo codice di errore, puoi facilmente sostituirlo

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

con

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

Controlla caso contrario, il Sandeep Bhandari 's risposta per quanto riguarda come creare un tipo di errore personalizzato


15
Ho appena ottengo l'errore: Error cannot be created because it has no accessible initializers.
Supertecnoboff

@AbhishekThapliyal potresti per favore elaborare un po 'di più il tuo commento? Non riesco a capire cosa intendi.
Luca D'Alberti

2
@ LucaD'Alberti come in Swift 4 non è possibile creare l'errore che mostra perché non ha inizializzatori accessibili, durante la creazione dell'Oggetto Errore.
Maheep

1
@ Maheep quello che sto suggerendo nella mia risposta è di non usare Error, ma NSError. Ovviamente l'utilizzo Errorgenera un errore.
Luca D'Alberti

L'errore è il protocollo. Non può essere istanziato direttamente.
slobodans

52

Puoi creare enumerazioni per gestire gli errori :)

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

e quindi creare un metodo all'interno di enum per ricevere il codice di risposta http e restituire l'errore corrispondente in cambio :)

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

Infine aggiorna il tuo blocco errori per accettare un singolo parametro di tipo RikhError :)

Ho un tutorial dettagliato su come ristrutturare il tradizionale modello di rete Objective-C basato su Object Oriented al moderno modello Protocol Oriented usando Swift3 qui https://learnwithmehere.blogspot.in Dai un'occhiata :)

Spero che sia d'aiuto :)


Ahh ma questo non dovrà obbligarmi a gestire manualmente tutti i casi? Cioè digitare i codici di errore?
Rikh

Sì, devi: D Ma allo stesso tempo puoi eseguire varie azioni specifiche per ogni stato di errore :) ora hai un controllo accurato sul modello di errore se nel caso non lo desideri puoi usare il caso 400 ... 404 {...} gestire solo casi generici :)
Sandeep Bhandari

Ah sì! Grazie
Rikh

Supponendo che più codici http non debbano puntare allo stesso caso, dovresti essere in grado di enumerare RikhError: Int, Error {case invalidRequest = 400} e quindi crearlo RikhError (rawValue: httpCode)
Brian F Leighty

51

Dovresti usare l'oggetto NSError.

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

Quindi lancia NSError all'oggetto Error


29

Dettagli

  • Versione Xcode 10.2.1 (10E1001)
  • Swift 5

Soluzione di errori di organizzazione in un'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
        }
    }
}

Utilizzo

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

16

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

Nota che la semplice implementazione di Error, ad esempio, come descritto in una delle risposte, fallirà (almeno in Swift 3) e la chiamata di localizedDescription risulterà nella stringa "Impossibile completare l'operazione. (Errore .StringError 1.) "


Dovrebbe essere mMsg = msg
Brett

1
Ops, giusto. Ho cambiato "msg" in "descrizione", che si spera sia un po 'più chiaro dell'originale.
prewett

4
Puoi ridurlo a struct StringError : LocalizedError { public let errorDescription: String? }, e usarlo semplicemente comeStringError(errorDescription: "some message")
Koen.

7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

creare un oggetto NSError e digitarlo su Error, mostrarlo ovunque

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

6

Continuo a pensare che la risposta di Harry sia la più semplice e completa, ma se hai bisogno di qualcosa di ancora più semplice, usa:

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

E usalo o provalo in questo modo:

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

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}

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

1

So che sei già soddisfatto di una risposta, ma se sei interessato a conoscere l'approccio giusto, questo potrebbe esserti utile. Preferirei non mescolare il codice di errore di risposta http con il codice di errore nell'oggetto errore (confuso? Continua a leggere un po '...).

I codici di risposta http sono codici di errore standard relativi a una risposta http che definiscono situazioni generiche quando viene ricevuta la risposta e variano da 1xx a 5xx (ad es. 200 OK, 408 Request timeout, 504 Gateway timeout ecc - http://www.restapitutorial.com/ httpstatuscodes.html )

Il codice di errore in un oggetto NSError fornisce un'identificazione molto specifica del tipo di errore descritto dall'oggetto per un particolare dominio dell'applicazione / prodotto / software. Ad esempio, la tua applicazione potrebbe utilizzare 1000 per "Spiacenti, non puoi aggiornare questo record più di una volta al giorno" o dire 1001 per "È necessario il ruolo di gestore per accedere a questa risorsa" ... che sono specifici per il tuo dominio / applicazione logica.

Per un'applicazione molto piccola, a volte questi due concetti vengono uniti. Ma sono completamente diversi come puoi vedere e molto importanti e utili per progettare e lavorare con software di grandi dimensioni.

Quindi, ci possono essere due tecniche per gestire il codice in modo migliore:

1. Il callback di completamento eseguirà tutti i controlli

completionHandler(data, httpResponse, responseError) 

2. Il metodo decide la situazione di successo e di errore e quindi richiama il callback corrispondente

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

Buona codifica :)


Quindi in pratica quello che stai cercando di dire è passare il parametro "data" nel caso in cui ci sia una stringa specifica da visualizzare in caso di un codice di errore specifico come restituito dal server? (Scusa, a volte posso essere un po 'lento!)
Rikh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.