Створіть свій власний код помилки швидко в 3


88

Я намагаюся швидко виконати URLSessionзапит 3. Я виконую цю дію в окремій функції (щоб не писати код окремо для GET і POST) і повертаю URLSessionDataTaskта обробляю успіх і невдачу при закритті. Начебто так-

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

Я не хочу обробляти умови помилки в цій функції, і хочу генерувати помилку, використовуючи код відповіді, і повертати цю помилку, щоб обробляти її всюди, звідки ця функція викликана. Хто-небудь може сказати мені, як це робити? Або це не «швидкий» спосіб вирішення таких ситуацій?


Спробуйте використовувати NSErrorзамість Errorу декларації ( var errorTemp = NSError(...))
Luca D'Alberti

Це вирішує проблему, але я думав, що swift 3 не хоче продовжувати використовувати NS?
Ріх,

Це робиться в розробці iOS. Для чистого розвитку Swift вам слід створити власний екземпляр помилки, відповідаючи Errorпротоколу
Luca D'Alberti

@ LucaD'Alberti Що ж, ваше рішення вирішило проблему, сміливо додайте його як відповідь, щоб я міг прийняти це!
Ріх,

Відповіді:


73

Ви можете створити протокол, що відповідає LocalizedErrorпротоколу Swift , з такими значеннями:

protocol OurErrorProtocol: LocalizedError {

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

Потім це дозволяє нам створювати конкретні помилки наступним чином:

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
а) немає необхідності створювати OurErrorProtocol, просто попросіть CustomError реалізувати помилку безпосередньо. б) це не працює (принаймні в Swift 3: localizedDescription ніколи не викликається, і ви отримуєте "Операцію не вдалося завершити."). Натомість вам потрібно реалізувати LocalizedError; див. мою відповідь.
prewett

@prewett Я щойно помітив, але ти маєш рацію! Реалізація поля errorDescription у LocalizedError фактично встановлює повідомлення, а не використовує мій метод, як описано вище. Однак я все ще зберігаю обгортку "OurErrorProtocol", оскільки мені потрібно також поле з локалізованим заголовком. Дякуємо, що вказали на це!
Harry Bloom

106

У вашому випадку помилка полягає в тому, що ви намагаєтеся створити Errorекземпляр. Errorв Swift 3 - це протокол, за допомогою якого можна визначити власну помилку. Ця функція спеціально підходить для чистих програм Swift, що працюють в різних ОС.

У розробці iOS NSErrorклас все ще доступний, і він відповідає Errorпротоколу.

Отже, якщо ваша мета - лише поширити цей код помилки, ви можете легко його замінити

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

з

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

В іншому випадку перевірте на SANDEEP Bhandari «S відповідь про те , як створити власний тип помилки


15
Я просто отримую помилку: Error cannot be created because it has no accessible initializers.
Supertecnoboff

@AbhishekThapliyal, не могли б Ви, детальніше, додати свій коментар? Я не можу зрозуміти, що ти маєш на увазі.
Лука Д'Альберті,

2
@ LucaD'Alberti, як у Swift 4, його показ помилки не може бути створений, оскільки він не має доступних ініціалізаторів під час створення об'єкта помилки.
Махіп,

1
@Maheep, те, що я пропоную у своїй відповіді, - не використовувати Error , а NSError. Звичайно, використання Errorвидає помилку.
Лука Д'Альберті

Помилка - це протокол. Неможливо безпосередньо створити примірник.
slobodans

52

Ви можете створити переліки для вирішення помилок :)

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

а потім створити метод всередині enum для отримання коду відповіді http і повернення відповідної помилки у відповідь :)

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

Нарешті оновіть свій блок відмов, щоб прийняти один параметр типу RikhError :)

У мене є детальний підручник про те, як реструктурувати традиційну об’єктно-орієнтовану мережеву модель на основі Objective C на сучасну модель, орієнтовану на протокол, за допомогою Swift3 тут https://learnwithmehere.blogspot.in Погляньте :)

Сподіваюся, це допоможе :)


Ага, але хіба це не змусить мене вручну розглядати всі справи? Тобто введіть коди помилок?
Ріх,

Так, вам потрібно: D Але в той же час ви можете вживати різні дії, характерні для кожного стану помилки :) Тепер ви чудово контролюєте модель помилок, якщо у випадку, якщо ви цього не хочете, ви можете використовувати випадок 400 ... 404 {...} розглядати просто загальні справи :)
Сандіп Бхандарі,

Ага, так! Подяка
Ріх

Якщо припустити, що декілька http-кодів не потрібно вказувати на один і той же випадок, ви повинні мати можливість просто перерахувати RikhError: Int, Error {case invalidRequest = 400}, а потім створити його RikhError (rawValue: httpCode)
Брайан Ф Лейтсі

48

Ви повинні використовувати об'єкт NSError.

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

Потім перекиньте NSError до об’єкта Error


27

Деталі

  • Версія Xcode 10.2.1 (10E1001)
  • Стрімкий 5

Рішення проблем організації в програмі

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

Використання

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

Впровадити 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
    }
}

Зверніть увагу, що просто реалізація Error, наприклад, як описано в одному з відповідей, не вдасться (принаймні в Swift 3), і виклик localizedDescription призведе до рядка "Операція не може бути завершена. (.StringError error 1.) "


Якщо це буде mMsg = msg
Бретт,

1
На жаль, правильно. Я змінив "msg" на "description", що, сподіваюся, трохи зрозуміліше мого оригіналу.
prewett

4
Ви можете зменшити це до struct StringError : LocalizedError { public let errorDescription: String? }, і це просто використовувати якStringError(errorDescription: "some message")
Koen.

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

створіть об'єкт NSError і введіть його в Error, покажіть де завгодно

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

4

Я все ще вважаю, що відповідь Гаррі найпростіша і завершена, але якщо вам потрібно щось ще простіше, то скористайтеся:

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

І використовуйте або протестуйте це так:

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

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

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

1

Я знаю, що ви вже задоволені відповіддю, але якщо вам цікаво знати правильний підхід, то це може бути вам корисно. Я волів би не змішувати код помилки http-відповіді з кодом помилки в об'єкті помилки (заплутаний? Будь ласка, продовжуйте читати трохи ...).

Коди відповідей http - це стандартні коди помилок щодо відповіді http, що визначають загальні ситуації при отриманні відповіді та варіюються від 1xx до 5xx (наприклад, 200 OK, 408 Запит минув, час очікування 504 шлюзу тощо - http://www.restapitutorial.com/ httpstatuscodes.html )

Код помилки в об'єкті NSError забезпечує дуже конкретну ідентифікацію виду помилки, яку об'єкт описує для конкретного домену програми / продукту / програмного забезпечення. Наприклад, ваша програма може використовувати 1000 для "Вибачте, ви не можете оновлювати цей запис частіше одного разу на день" або вимовляти 1001 для "Вам потрібна роль менеджера для доступу до цього ресурсу" ... які стосуються вашого домену / програми логіка.

Для дуже маленької програми іноді ці дві концепції об’єднують. Але вони абсолютно різні, як бачите, і дуже важливі та корисні при розробці та роботі з великим програмним забезпеченням.

Отже, може бути дві методики для кращої обробки коду:

1. Зворотний виклик завершення виконає всі перевірки

completionHandler(data, httpResponse, responseError) 

2. Ваш метод вирішує ситуацію успіху та помилки, а потім викликає відповідний зворотний виклик

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

Щасливого кодування :)


Отже, в основному те, що ви намагаєтесь сказати, - це передати параметр "data" на випадок, якщо буде відображено якийсь рядок у випадку конкретного коду помилки, який повертається із сервера? (Вибачте, часом я можу бути дещо повільним!)
Ріх,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.