Tạo mã lỗi của riêng bạn trong swift 3


88

Những gì tôi đang cố gắng đạt được là thực hiện một URLSessionyêu cầu trong nhanh chóng 3. Tôi đang thực hiện hành động này trong một chức năng riêng biệt (để không viết mã riêng cho GET và POST) và trả về URLSessionDataTaskvà xử lý thành công và thất bại trong các lần đóng. Đại loại như thế này-

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

Tôi không muốn xử lý tình trạng lỗi trong hàm này và muốn tạo ra lỗi bằng cách sử dụng mã phản hồi và trả về Lỗi này để xử lý nó ở bất cứ nơi nào hàm này được gọi từ. Ai có thể cho tôi biết làm thế nào để đi về điều này? Hay đây không phải là cách "Swift" để xử lý những tình huống như vậy?


Hãy thử sử dụng NSErrorthay vì Errortrong tờ khai ( var errorTemp = NSError(...))
Luca D'Alberti

Điều đó giải quyết được vấn đề nhưng tôi nghĩ rằng swift 3 không muốn tiếp tục sử dụng NS?
Rikh

Nó có trong phát triển iOS. Đối với phát triển Swift tinh khiết bạn nên tạo Ví dụ lỗi của riêng bạn bằng cách tuân các Errorgiao thức
Luca D'Alberti

@ LucaD'Alberti Chà giải pháp của bạn đã giải quyết được vấn đề, vui lòng thêm nó làm câu trả lời để tôi có thể chấp nhận nó!
Rikh

Câu trả lời:


72

Bạn có thể tạo một giao thức, tuân theo LocalizedErrorgiao thức Swift , với các giá trị sau:

protocol OurErrorProtocol: LocalizedError {

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

Sau đó, điều này cho phép chúng tôi tạo ra các lỗi cụ thể như sau:

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) Không cần thiết phải tạo OurErrorProtocol, chỉ cần có CustomError triển khai trực tiếp Lỗi. b) điều này không hoạt động (ít nhất là trong Swift 3: localizedDescription không bao giờ được gọi và bạn nhận được "Không thể hoàn thành thao tác."). Thay vào đó, bạn cần triển khai LocalizedError; xem câu trả lời của tôi.
prewett

@prewett Tôi chỉ để ý nhưng bạn nói đúng! Trên thực tế, việc triển khai trường errorDescription trong LocalizedError sẽ đặt thông báo thay vì sử dụng phương pháp của tôi như đã mô tả ở trên. Tuy nhiên, tôi vẫn giữ trình bao bọc "OurErrorProtocol", vì tôi cũng cần trường localizedTitle. Cảm ơn vì đã chỉ ra điều đó!
Harry Bloom

106

Trong trường hợp của bạn, lỗi là bạn đang cố tạo một Errorphiên bản. Errortrong Swift 3 là một giao thức có thể được sử dụng để xác định một lỗi tùy chỉnh. Tính năng này đặc biệt dành cho các ứng dụng Swift thuần túy chạy trên các hệ điều hành khác nhau.

Trong quá trình phát triển iOS, NSErrorlớp này vẫn có sẵn và nó tuân theo Errorgiao thức.

Vì vậy, nếu mục đích của bạn chỉ là tuyên truyền mã lỗi này, bạn có thể dễ dàng thay thế

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

với

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

Nếu không, hãy kiểm tra câu trả lời của Sandeep Bhandari về cách tạo loại lỗi tùy chỉnh


15
Tôi chỉ nhận được lỗi: Error cannot be created because it has no accessible initializers.
Supertecnoboff

@AbhishekThapliyal, bạn có thể vui lòng giải thích thêm một chút nhận xét của mình được không? Tôi không thể hiểu ý bạn.
Luca D'Alberti

2
@ LucaD'Alberti như trong Swift 4, Lỗi hiển thị của nó không thể được tạo ra vì nó không có trình khởi tạo có thể truy cập được trong khi tạo Đối tượng Lỗi.
Maheep

1
@Maheep những gì tôi đề xuất trong câu trả lời của mình không phải là sử dụng Error, nhưng NSError. Tất nhiên bằng cách sử dụng Errorném một lỗi.
Luca D'Alberti

Lỗi là giao thức. Không thể được khởi tạo trực tiếp.
slobodans

52

Bạn có thể tạo enum để xử lý lỗi :)

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

và sau đó tạo một phương thức bên trong enum để nhận mã phản hồi http và trả lại lỗi tương ứng :)

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

Cuối cùng cập nhật khối lỗi của bạn để chấp nhận tham số duy nhất của loại RikhError :)

Tôi có một bài hướng dẫn chi tiết về cách cấu trúc lại mô hình mạng Hướng đối tượng dựa trên Objective - C truyền thống sang mô hình hướng đối tượng theo giao thức hiện đại sử dụng Swift3 tại đây https://learnwithmehere.blogspot.in Chúc các bạn xem :)

Hy vọng nó giúp :)


Ahh nhưng điều này sẽ không khiến tôi phải tự xử lý tất cả các trường hợp sao? Đó là gõ mã lỗi?
Rikh

Đúng, bạn phải làm thế: D Nhưng đồng thời bạn có thể thực hiện nhiều hành động khác nhau cụ thể cho từng trạng thái lỗi :) bây giờ bạn có quyền kiểm soát tốt mô hình lỗi nếu trong trường hợp bạn không muốn làm điều đó, bạn có thể sử dụng trường hợp 400 ... 404 {...} chỉ xử lý các trường hợp chung chung :)
Sandeep Bhandari

Ahh đúng! Cảm ơn
Rikh

Giả sử nhiều mã http không cần trỏ đến cùng một trường hợp, bạn sẽ có thể chỉ thực hiện enum RikhError: Int, Error {case invalidRequest = 400} và sau đó tạo nó RikhError (rawValue: httpCode)
Brian F Leighty

48

Bạn nên sử dụng đối tượng NSError.

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

Sau đó, truyền NSError thành đối tượng Lỗi


27

Chi tiết

  • Phiên bản Xcode 10.2.1 (10E1001)
  • Swift 5

Giải pháp sắp xếp lỗi trong ứng dụng

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

Sử dụng

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

Triển khai 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
    }
}

Lưu ý rằng chỉ cần triển khai Error, chẳng hạn như được mô tả trong một trong các câu trả lời, sẽ không thành công (ít nhất là trong Swift 3) và việc gọi localizedDescription sẽ dẫn đến chuỗi "Không thể hoàn thành thao tác. (Lỗi .StringError 1) "


Đó có nên là mMsg = msg
Brett

1
Rất tiếc, đúng. Tôi đã thay đổi "msg" thành "description", hy vọng nó rõ ràng hơn một chút so với bản gốc của tôi.
prewett

4
Bạn có thể giảm số đó xuống struct StringError : LocalizedError { public let errorDescription: String? }, và chỉ cần sử dụng với tênStringError(errorDescription: "some message")
Koen.

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

tạo một đối tượng NSError và gõ nó thành Lỗi, hiển thị nó ở bất kỳ đâu

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

4

Tôi vẫn nghĩ rằng câu trả lời của Harry là đơn giản nhất và đã hoàn thành nhưng nếu bạn cần thứ gì đó thậm chí còn đơn giản hơn, thì hãy sử dụng:

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

Và sử dụng hoặc kiểm tra nó như thế này:

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

Tôi biết bạn đã hài lòng với một câu trả lời nhưng nếu bạn muốn biết cách tiếp cận phù hợp, thì điều này có thể hữu ích cho bạn. Tôi không muốn kết hợp mã lỗi http-response với mã lỗi trong đối tượng lỗi (nhầm lẫn? Vui lòng tiếp tục đọc một chút ...).

Mã phản hồi http là mã lỗi chuẩn về phản hồi http xác định các tình huống chung khi nhận được phản hồi và thay đổi từ 1xx đến 5xx (ví dụ: 200 OK, 408 Yêu cầu hết thời gian chờ, 504 Gateway hết thời gian chờ, v.v. - http://www.restapitutorial.com/ httpstatuscodes.html )

Mã lỗi trong đối tượng NSError cung cấp nhận dạng rất cụ thể đối với loại lỗi mà đối tượng mô tả cho một miền ứng dụng / sản phẩm / phần mềm cụ thể. Ví dụ: ứng dụng của bạn có thể sử dụng 1000 cho "Rất tiếc, bạn không thể cập nhật bản ghi này nhiều lần trong ngày" hoặc nói 1001 cho "Bạn cần vai trò người quản lý để truy cập tài nguyên này" ... dành riêng cho miền / ứng dụng của bạn Hợp lý.

Đối với một ứng dụng rất nhỏ, đôi khi hai khái niệm này được hợp nhất. Nhưng chúng hoàn toàn khác nhau như bạn có thể thấy và rất quan trọng và hữu ích để thiết kế và làm việc với phần mềm lớn.

Vì vậy, có thể có hai kỹ thuật để xử lý mã theo cách tốt hơn:

1. Lệnh gọi lại hoàn thành sẽ thực hiện tất cả các kiểm tra

completionHandler(data, httpResponse, responseError) 

2. Phương pháp của bạn quyết định thành công và tình huống lỗi, sau đó gọi lại tương ứng

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

Chúc bạn viết mã vui vẻ :)


Vì vậy, về cơ bản những gì bạn đang cố gắng nói là truyền tham số "data" trong trường hợp có một số chuỗi cụ thể được hiển thị trong trường hợp có mã lỗi cụ thể được trả về từ máy chủ? (Xin lỗi, đôi khi tôi có thể hơi chậm!)
Rikh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.