Wygeneruj swój własny kod błędu w Swift 3

88

To, co staram się osiągnąć, to wykonać URLSessionżądanie w swift 3. Wykonuję tę akcję w osobnej funkcji (aby nie pisać kodu osobno dla GET i POST) i zwracam URLSessionDataTaski obsługuję sukces i niepowodzenie w domknięciach. Coś w ten sposób-

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

Nie chcę obsługiwać warunku błędu w tej funkcji i chcę wygenerować błąd przy użyciu kodu odpowiedzi i zwrócić ten błąd, aby obsłużyć go, gdziekolwiek ta funkcja jest wywoływana. Czy ktoś może mi powiedzieć, jak się do tego zabrać? A może nie jest to „szybki” sposób radzenia sobie w takich sytuacjach?

Rikh
źródło
Spróbuj użyć NSErrorzamiast Errorw deklaracji ( var errorTemp = NSError(...))
Luca D'Alberti
To rozwiązuje problem, ale pomyślałem, że Swift 3 nie chce dalej używać NS?
Rikh
Działa w programowaniu na iOS. Dla czystego rozwoju Swift powinieneś stworzyć własną instancję błędu, zgodnie z Errorprotokołem
Luca D'Alberti
@ LucaD'Alberti Cóż, twoje rozwiązanie rozwiązało problem, możesz je dodać jako odpowiedź, abym mógł je zaakceptować!
Rikh

Odpowiedzi:

72

Możesz utworzyć protokół zgodny z LocalizedErrorprotokołem Swift z następującymi wartościami:

protocol OurErrorProtocol: LocalizedError {

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

To pozwala nam tworzyć konkretne błędy, takie jak:

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
źródło
3
a) nie jest konieczne tworzenie OurErrorProtocol, wystarczy, że CustomError bezpośrednio zaimplementuje Error. b) to nie działa (przynajmniej w Swift 3: localizedDescription nie jest nigdy wywoływana i pojawia się komunikat „Operacja nie mogła zostać zakończona”). Zamiast tego musisz zaimplementować LocalizedError; zobacz moją odpowiedź.
prewett
@prewett Właśnie zauważyłem, ale masz rację! Zaimplementowanie pola errorDescription w LocalizedError w rzeczywistości ustawia komunikat, zamiast używać mojej metody, jak opisano powyżej. Nadal jednak zachowuję opakowanie „OurErrorProtocol”, ponieważ potrzebuję również pola localizedTitle. Dziękuję za zwrócenie uwagi!
Harry Bloom,
106

W Twoim przypadku błąd polega na tym, że próbujesz wygenerować Errorinstancję. Errorw Swift 3 to protokół, którego można użyć do zdefiniowania niestandardowego błędu. Ta funkcja jest szczególnie przydatna dla czystych aplikacji Swift, które działają w różnych systemach operacyjnych.

W rozwoju iOS NSErrorklasa jest nadal dostępna i jest zgodna z Errorprotokołem.

Jeśli więc Twoim celem jest tylko propagowanie tego kodu błędu, możesz łatwo go zastąpić

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

z

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

W przeciwnym razie sprawdzić Sandeep Bhandari „s odpowiedzi dotyczące sposobu tworzenia niestandardowego typu błędu

Luca D'Alberti
źródło
15
Właśnie pojawia się błąd: Error cannot be created because it has no accessible initializers.
Supertecnoboff
@AbhishekThapliyal, czy mógłbyś nieco bardziej rozwinąć swój komentarz? Nie rozumiem, co masz na myśli.
Luca D'Alberti
2
@ LucaD'Alberti, podobnie jak w Swift 4, jego pokazujący błąd nie może zostać utworzony, ponieważ nie ma dostępnych inicjatorów podczas tworzenia obiektu błędu.
Maheep
1
@Maheep, co sugeruję w mojej odpowiedzi, to nie używać Error, ale NSError. Oczywiście użycie Errorpowoduje błąd.
Luca D'Alberti
Błąd jest protokołem. Nie można utworzyć wystąpienia bezpośrednio.
slobodany
52

Możesz tworzyć wyliczenia, aby radzić sobie z błędami :)

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

a następnie utwórz metodę wewnątrz enum, aby otrzymać kod odpowiedzi http i zwrócić w odpowiedzi odpowiedni błąd :)

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

Na koniec zaktualizuj blok błędów, aby akceptował pojedynczy parametr typu RikhError :)

Mam szczegółowy samouczek na temat restrukturyzacji tradycyjnego modelu sieci Objective opartego na C do nowoczesnego modelu zorientowanego na protokół przy użyciu Swift3 tutaj https://learnwithmehere.blogspot.in Spójrz :)

Mam nadzieję, że to pomoże :)

Sandeep Bhandari
źródło
Ahh, ale czy to nie będzie musiało zmuszać mnie do ręcznej obsługi wszystkich spraw? To jest wpisać kody błędów?
Rikh
Tak, musisz: D Ale w tym samym czasie możesz podjąć różne działania specyficzne dla każdego statusu błędu :) teraz masz dokładną kontrolę nad modelem błędu, jeśli nie chcesz tego robić, możesz użyć przypadku 400 ... 404 {...} zajmuj się tylko zwykłymi przypadkami :)
Sandeep Bhandari
Ach tak! Dzięki
Rikh
Zakładając, że wiele kodów http nie musi wskazywać na ten sam przypadek, powinno być możliwe wykonanie wyliczenia RikhError: Int, Error {case invalidRequest = 400}, a następnie utworzenie go RikhError (rawValue: httpCode)
Brian F Leighty
48

Powinieneś użyć obiektu NSError.

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

Następnie rzuć NSError na obiekt Error

Ahmed Lotfy
źródło
27

Detale

  • Xcode w wersji 10.2.1 (10E1001)
  • Szybki 5

Rozwiązanie problemu organizacji błędów w aplikacji

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

Stosowanie

//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)
}
Wasilij Bodnarczuk
źródło
16

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

Zwróć uwagę, że samo zaimplementowanie na przykład błędu, jak opisano w jednej z odpowiedzi, zakończy się niepowodzeniem (przynajmniej w języku Swift 3), a wywołanie localizedDescription spowoduje wyświetlenie ciągu „Operacja nie mogła zostać zakończona. (.StringError error 1.) "

prewett
źródło
Czy to powinno być mMsg = msg
Brett
1
Ups, racja. Zmieniłem „msg” na „description”, co mam nadzieję, że jest nieco jaśniejsze niż mój oryginał.
prewett
4
Możesz to zredukować struct StringError : LocalizedError { public let errorDescription: String? }, a to po prostu użyj jakoStringError(errorDescription: "some message")
Koen.
7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

utwórz obiekt NSError i typecast na Error, pokaż go w dowolnym miejscu

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

Nadal uważam, że odpowiedź Harry'ego jest najprostsza i kompletna, ale jeśli potrzebujesz czegoś jeszcze prostszego, użyj:

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

I użyj lub przetestuj w ten sposób:

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

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}
Reimond Hill
źródło
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
źródło
1

Wiem, że odpowiedź była już zadowalająca, ale jeśli chcesz poznać właściwe podejście, może to być pomocne. Wolałbym nie mieszać kodu błędu odpowiedzi http z kodem błędu w obiekcie błędu (zdezorientowany? Proszę kontynuować czytanie trochę ...).

Kody odpowiedzi http to standardowe kody błędów dotyczące odpowiedzi http definiujące ogólne sytuacje, w których otrzymywana jest odpowiedź, i wahają się od 1xx do 5xx (np. 200 OK, przekroczono limit czasu 408 żądania, przekroczono limit czasu bramy 504 itp. - http://www.restapitutorial.com/ httpstatuscodes.html )

Kod błędu w obiekcie NSError zapewnia bardzo dokładną identyfikację rodzaju błędu opisywanego przez obiekt dla określonej domeny aplikacji / produktu / oprogramowania. Na przykład Twoja aplikacja może używać 1000 dla „Przepraszamy, nie możesz aktualizować tego rekordu więcej niż raz dziennie” lub powiedzieć 1001 dla „Potrzebujesz roli menedżera, aby uzyskać dostęp do tego zasobu” ... które są specyficzne dla Twojej domeny / aplikacji logika.

W przypadku bardzo małej aplikacji czasami te dwa pojęcia są łączone. Ale są one zupełnie inne, jak widać, i są bardzo ważne i pomocne w projektowaniu i pracy z dużym oprogramowaniem.

Tak więc mogą istnieć dwie techniki lepszej obsługi kodu:

1. Wywołanie zwrotne zakończenia przeprowadzi wszystkie sprawdzenia

completionHandler(data, httpResponse, responseError) 

2. Twoja metoda decyduje o sukcesie i sytuacji błędu, a następnie wywołuje odpowiednie wywołanie zwrotne

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

Miłego kodowania :)

Tushar
źródło
Więc w zasadzie to, co próbujesz powiedzieć, to przekazanie parametru „data” na wypadek, gdyby pojawił się jakiś określony ciąg znaków do wyświetlenia w przypadku określonego kodu błędu zwróconego z serwera? (Przepraszam, czasami mogę być trochę powolny!)
Rikh