在Swift 3中生成自己的错误代码

120

我希望达成的目标是在Swift 3中执行一个URLSession请求。我将此操作放在一个单独的函数中(以免为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!)
               }
          }
     }
}

我不想在这个函数中处理错误条件,希望使用响应代码生成错误并返回该错误,在调用该函数的任何地方处理它。有人能告诉我如何处理吗?或者这不是“Swift”的处理此类情况的方式吗?


尝试在声明中使用NSError而不是Errorvar errorTemp = NSError(...))。 - Luca D'Alberti
这解决了问题,但我认为Swift 3不希望继续使用NS? - Rikh
在iOS开发中,它是可行的。对于纯Swift开发,您应该通过符合“Error”协议来创建自己的错误实例。 - Luca D'Alberti
@LucaD'Alberti,你的解决方案确实解决了问题,请随意将其添加为答案,以便我可以接受它! - Rikh
10个回答

124
在你的情况下,错误在于你正在尝试生成一个Error实例。在Swift 3中,Error是一个协议,可用于定义自定义错误。此功能特别适用于在不同操作系统上运行的纯Swift应用程序。
在iOS开发中,NSError类仍然可用,并符合Error协议。
因此,如果你的目的只是传播这个错误代码,你可以很容易地进行替换。
var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

var errorTemp = NSError(domain:"", code:httpResponse.statusCode, userInfo:nil)
否则,请查看Sandeep Bhandari答案,了解如何创建自定义错误类型。

27
我刚刚遇到了这个错误:Error cannot be created because it has no accessible initializers。该错误表示无法创建对象,因为它没有可访问的初始化器。 - Supertecnoboff
@AbhishekThapliyal,您能否详细解释一下您的评论?我不明白您的意思。 - Luca D'Alberti
2
在Swift 4中,当创建Error对象时,会显示错误无法创建,因为它没有可访问的初始化程序。@LucaD'Alberti - Maheep
2
@Maheep,我在我的回答中建议的不是使用Error,而是NSError。当然,使用Error会抛出错误。 - Luca D'Alberti
错误是协议,不能直接实例化。 - slobodans

88

你可以创建一个协议,符合Swift的LocalizedError协议,并使用这些值:

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
a) 不必创建 OurErrorProtocol,只需让 CustomError 直接实现 Error 协议即可。 b) 这样做行不通(至少在 Swift 3 中):localizedDescription 永远不会被调用,并且会得到“操作无法完成”的错误信息。你需要实现 LocalizedError 接口,请参考我的答案。 - prewett
@prewett 我刚刚注意到你是对的!实际上,在LocalizedError中实现errorDescription字段会设置消息,而不是使用我上面描述的方法。但我仍然保留“ OurErrorProtocol”包装器,因为我还需要localizedTitle字段。感谢您指出这一点! - Harry Bloom

76

你应该使用NSError对象。

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

然后将NSError转换为Error对象。


62

你可以创建枚举类型来处理错误 :)

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

然后在枚举内创建一个方法,接收 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的单一参数 :)

我在这里有一个详细的教程,介绍如何使用Swift3重构传统的基于Objective-C的面向对象网络模型为现代协议导向模型 https://learnwithmehere.blogspot.in 请查看 :)

希望它有所帮助 :)


啊,但这不是要让我手动处理所有情况吗?也就是说,要输入错误代码? - Rikh
是的,你必须这样做:D 但同时,你可以针对每个错误状态采取各种特定的操作:) 现在,如果你不想这样做,你可以使用 case 400 ... 404 {...} 处理一般情况 :) - Sandeep Bhandari
假设多个HTTP代码不需要指向相同的情况,您应该能够只需执行enum RikhError:Int,Error {case invalidRequest = 400},然后创建它RikhError(rawValue:httpCode)。 - Brian F Leighty

41

详情

  • Xcode 版本为 10.2.1 (10E1001)
  • Swift 版本为 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)
}

18

实现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将会导致字符串"The operation could not be completed. (.StringError error 1.)"



Original Answer:

应该是 mMsg = msg。 - Brett
1
{btsdaf} - prewett
4
你可以将其简化为 struct StringError : LocalizedError { public let errorDescription: String? },然后只需使用 StringError(errorDescription: "some message") 即可。 - Koen.

13

我仍然认为Harry的回答是最简单和完整的,但如果你需要更简单的东西,那么可以使用:

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

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

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

2
我知道你已经得到了一个答案,但如果你想知道正确的方法,那么这可能对你有所帮助。我更喜欢不混淆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”参数?(抱歉,有时我可能会有点慢!) - Rikh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接