Gere seu próprio código de erro em swift 3

88

O que estou tentando fazer é executar uma URLSessionsolicitação em swift 3. Estou executando essa ação em uma função separada (de modo a não escrever o código separadamente para GET e POST) e retornar URLSessionDataTaske lidar com o sucesso e a falha nos fechamentos. Mais ou menos assim-

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

Não desejo manipular a condição de erro nesta função e desejo gerar um erro usando o código de resposta e retornar este Erro para manipulá-lo de onde quer que esta função seja chamada. Alguém pode me dizer como fazer isso? Ou não é essa a maneira "rápida" de lidar com essas situações?

Rikh
fonte
Tente usar em NSErrorvez de Errorna declaração ( var errorTemp = NSError(...))
Luca D'Alberti
Isso resolve o problema, mas pensei que o swift 3 não deseja continuar usando o NS?
Rikh de
Faz no desenvolvimento iOS. Para desenvolvimento Swift puro, você deve criar sua própria instância de erro em conformidade com o Errorprotocolo
Luca D'Alberti
@ LucaD'Alberti Bem, sua solução resolveu o problema, fique à vontade para adicioná-la como uma resposta para que eu possa aceitá-la!
Rikh de

Respostas:

72

Você pode criar um protocolo, em conformidade com o LocalizedErrorprotocolo Swift , com estes valores:

protocol OurErrorProtocol: LocalizedError {

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

Isso nos permite criar erros concretos como:

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
fonte
3
a) não é necessário criar OurErrorProtocol, basta que CustomError implemente Error diretamente. b) isso não funciona (pelo menos no Swift 3: localizedDescription nunca é chamado e você obtém "A operação não pôde ser concluída."). Em vez disso, você precisa implementar LocalizedError; veja minha resposta.
prewett
@prewett Acabei de notar, mas você está certo! Implementar o campo errorDescription em LocalizedError de fato define a mensagem em vez de usar meu método conforme descrito acima. Ainda estou mantendo o wrapper "OurErrorProtocol", pois também preciso do campo localizedTitle. Obrigado por apontar isso!
Harry Bloom,
106

No seu caso, o erro é que você está tentando gerar uma Errorinstância. Errorem Swift 3 é um protocolo que pode ser usado para definir um erro personalizado. Esse recurso é especialmente para aplicativos Swift puros para rodar em sistemas operacionais diferentes.

No desenvolvimento iOS, a NSErrorclasse ainda está disponível e está em conformidade com o Errorprotocolo.

Portanto, se o seu objetivo é apenas propagar este código de erro, você pode facilmente substituir

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

com

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

Caso contrário, verifique a Sandeep Bhandari 's resposta a respeito de como criar um tipo de erro personalizada

Luca D'Alberti
fonte
15
Eu só recebo o erro: Error cannot be created because it has no accessible initializers.
Supertecnoboff
@AbhishekThapliyal poderia elaborar um pouco mais seu comentário? Não consigo entender o que você quer dizer.
Luca D'Alberti
2
@LucaD'Alberti como no Swift 4 está mostrando Erro não pode ser criado porque não tem inicializadores acessíveis, ao criar Objeto Erro.
Maheep de
1
@Maheep o que estou sugerindo em minha resposta não é usar Error, mas NSError. É claro que usar Errorgera um erro.
Luca D'Alberti de
O erro é o protocolo. Não pode ser instanciado diretamente.
slobodans
52

Você pode criar enums para lidar com erros :)

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

e, em seguida, crie um método dentro de enum para receber o código de resposta http e retornar o erro correspondente em retorno :)

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

Por fim, atualize seu bloco de falha para aceitar um parâmetro único do tipo RikhError :)

Eu tenho um tutorial detalhado sobre como reestruturar o modelo tradicional de rede Objective-C baseado em Object Oriented para o modelo moderno de Protocol Oriented usando Swift3 aqui https://learnwithmehere.blogspot.in Dê uma olhada :)

Espero que ajude :)

Sandeep Bhandari
fonte
Ahh, mas isso não vai ter que me fazer lidar manualmente com todos os casos? Isso é tipo os códigos de erro?
Rikh de
Sim, você deve: D Mas ao mesmo tempo, você pode realizar várias ações específicas para cada status de erro :) agora você tem um controle fino sobre o modelo de erro, se caso não queira fazer isso, você pode usar o caso 400 ... 404 {...} lidar apenas com casos genéricos :)
Sandeep Bhandari
Ahh sim! Obrigado
Rikh
Assumindo que vários códigos http não precisam apontar para o mesmo caso, você deve ser capaz de apenas enum RikhError: Int, Error {case invalidRequest = 400} e então criá-lo RikhError (rawValue: httpCode)
Brian F Leighty
48

Você deve usar o objeto NSError.

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

Em seguida, lance NSError para o objeto Error

Ahmed Lotfy
fonte
27

Detalhes

  • Xcode versão 10.2.1 (10E1001)
  • Swift 5

Solução de erros de organização em um aplicativo

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

Uso

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

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

Observe que simplesmente implementar Error, por exemplo, conforme descrito em uma das respostas, irá falhar (pelo menos no Swift 3) e chamar localizedDescription resultará na string "A operação não pôde ser concluída. (.StringError erro 1.) "

pré-molhado
fonte
Deve ser mMsg = msg
Brett
1
Opa, certo. Mudei "msg" para "descrição", que espero ser um pouco mais claro do que o original.
prewett
4
Você pode reduzir isso para struct StringError : LocalizedError { public let errorDescription: String? }, e simplesmente usar comoStringError(errorDescription: "some message")
Koen.
7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

crie um objeto NSError e digite-o para Erro, mostre-o em qualquer lugar

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

Ainda acho que a resposta de Harry é a mais simples e completa, mas se você precisar de algo ainda mais simples, use:

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 use ou teste assim:

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

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

Sei que você já ficou satisfeito com a resposta, mas se estiver interessado em saber a abordagem certa, isso pode ser útil para você. Eu preferiria não misturar o código de erro de resposta http com o código de erro no objeto de erro (confuso? Continue lendo um pouco ...).

Os códigos de resposta http são códigos de erro padrão sobre uma resposta http que definem situações genéricas quando a resposta é recebida e varia de 1xx a 5xx (por exemplo, 200 OK, 408 Request time out, 504 Gateway timeout etc - http://www.restapitutorial.com/ httpstatuscodes.html )

O código de erro em um objeto NSError fornece uma identificação muito específica para o tipo de erro que o objeto descreve para um determinado domínio de aplicativo / produto / software. Por exemplo, seu aplicativo pode usar 1000 para "Desculpe, você não pode atualizar este registro mais de uma vez por dia" ou 1001 para "Você precisa da função de gerente para acessar este recurso" ... que são específicos para seu domínio / aplicativo lógica.

Para um aplicativo muito pequeno, às vezes esses dois conceitos são mesclados. Mas eles são completamente diferentes, como você pode ver, e muito importantes e úteis para projetar e trabalhar com softwares grandes.

Portanto, pode haver duas técnicas para lidar com o código da melhor maneira:

1. O retorno de chamada de conclusão executará todas as verificações

completionHandler(data, httpResponse, responseError) 

2. Seu método decide o sucesso e a situação de erro e, em seguida, invoca o retorno de chamada correspondente

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

Boa codificação :)

Tushar
fonte
Então, basicamente o que você está tentando dizer é passar o parâmetro "data" caso haja alguma string específica a ser exibida no caso de um código de erro específico retornado do servidor? (Desculpe, às vezes posso ser um pouco lento!)
Rikh