Làm cách nào để cung cấp mô tả được bản địa hóa với loại Lỗi trong Swift?


202

Tôi đang xác định loại lỗi tùy chỉnh với cú pháp Swift 3 và tôi muốn cung cấp mô tả thân thiện với người dùng về lỗi được trả về bởi thuộc localizedDescriptiontính của Errorđối tượng. Tôi làm nó như thế nào?

public enum MyError: Error {
  case customError

  var localizedDescription: String {
    switch self {
    case .customError:
      return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
    }
  }
}

let error: Error = MyError.customError
error.localizedDescription
// "The operation couldn’t be completed. (MyError error 0.)"

Có cách nào localizedDescriptionđể trả về mô tả lỗi tùy chỉnh của tôi không ("Mô tả lỗi thân thiện với người dùng.")? Lưu ý rằng đối tượng lỗi ở đây là loại Errorvà không MyError. Tất nhiên, tôi có thể truyền đối tượng cho MyError

(error as? MyError)?.localizedDescription

Nhưng có cách nào để làm cho nó hoạt động mà không chuyển sang loại lỗi của tôi không?

Câu trả lời:


402

Như được mô tả trong ghi chú phát hành Xcode 8 beta 6,

Các loại lỗi do Swift xác định có thể cung cấp các mô tả lỗi cục bộ bằng cách áp dụng giao thức LocalizedError mới.

Trong trường hợp của bạn:

public enum MyError: Error {
    case customError
}

extension MyError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .customError:
            return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
        }
    }
}

let error: Error = MyError.customError
print(error.localizedDescription) // A user-friendly description of the error.

Bạn có thể cung cấp nhiều thông tin hơn nữa nếu lỗi được chuyển thành NSError(luôn luôn có thể):

extension MyError : LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .customError:
            return NSLocalizedString("I failed.", comment: "")
        }
    }
    public var failureReason: String? {
        switch self {
        case .customError:
            return NSLocalizedString("I don't know why.", comment: "")
        }
    }
    public var recoverySuggestion: String? {
        switch self {
        case .customError:
            return NSLocalizedString("Switch it off and on again.", comment: "")
        }
    }
}

let error = MyError.customError as NSError
print(error.localizedDescription)        // I failed.
print(error.localizedFailureReason)      // Optional("I don\'t know why.")
print(error.localizedRecoverySuggestion) // Optional("Switch it off and on again.")

Bằng cách áp dụng CustomNSErrorgiao thức, lỗi có thể cung cấp một userInfotừ điển (và cả a domaincode). Thí dụ:

extension MyError: CustomNSError {

    public static var errorDomain: String {
        return "myDomain"
    }

    public var errorCode: Int {
        switch self {
        case .customError:
            return 999
        }
    }

    public var errorUserInfo: [String : Any] {
        switch self {
        case .customError:
            return [ "line": 13]
        }
    }
}

let error = MyError.customError as NSError

if let line = error.userInfo["line"] as? Int {
    print("Error in line", line) // Error in line 13
}

print(error.code) // 999
print(error.domain) // myDomain

7
Có một lý do tại sao bạn thực hiện MyErrormột Errorđầu tiên và mở rộng nó với LocalizedErrorsau này? Có một sự khác biệt nếu bạn làm nó LocalizedErrorở nơi đầu tiên?
Gee.E

9
@ Gee.E: Không có gì khác biệt. Nó chỉ là một cách để tổ chức mã (một phần mở rộng cho mỗi giao thức). So sánh stackoverflow.com/questions/36263892/ Mạnh , stackoverflow.com/questions/40502086/ cường , hoặc natashatherobot.com/USE-swift-extensions .
Martin R

4
À, kiểm tra đi. Tôi hiểu những gì bạn đang nói bây giờ. Phần "Tuân thủ giao thức" trên natashatherobot.com/USE-swift-extensions thực sự là một ví dụ điển hình về ý nghĩa của bạn. Cảm ơn!
Gee.E

1
@MartinR Nếu lỗi của tôi sẽ được chuyển đổi thành NSError, làm thế nào tôi có thể vượt qua một từ điển từ lỗi có thể được truy cập như userInfo của NSError?
BangOperator

18
Cẩn thận để gõ var errorDescription: String?thay vì String. Có một lỗi trong việc triển khai LocalizedError. Xem SR-5858 .
ethanhuang13

35

Tôi cũng sẽ thêm, nếu lỗi của bạn có các tham số như thế này

enum NetworkError: LocalizedError {
  case responseStatusError(status: Int, message: String)
}

bạn có thể gọi các tham số này trong mô tả được bản địa hóa của bạn như thế này:

extension NetworkError {
  public var errorDescription: String? {
    switch self {
    case .responseStatusError(status: let status, message: let message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}

Bạn thậm chí có thể làm cho nó ngắn hơn như thế này:

extension NetworkError {
  public var errorDescription: String? {
    switch self {
    case let .responseStatusError(status, message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}

4

Hiện tại có hai giao thức chấp nhận Lỗi mà loại lỗi của bạn có thể áp dụng để cung cấp thông tin bổ sung cho Objective-C - LocalizedError và CustomNSError. Đây là một lỗi ví dụ thông qua cả hai:

enum MyBetterError : CustomNSError, LocalizedError {
    case oops

    // domain
    static var errorDomain : String { return "MyDomain" }
    // code
    var errorCode : Int { return -666 }
    // userInfo
    var errorUserInfo: [String : Any] { return ["Hey":"Ho"] };

    // localizedDescription
    var errorDescription: String? { return "This sucks" }
    // localizedFailureReason
    var failureReason: String? { return "Because it sucks" }
    // localizedRecoverySuggestion
    var recoverySuggestion: String? { return "Give up" }

}

2
Bạn có thể chỉnh sửa? Ví dụ của bạn không giúp nhiều để hiểu giá trị của mỗi. Hoặc chỉ xóa nó vì câu trả lời của MartinR cung cấp chính xác điều này ...
Honey

3

Sử dụng một cấu trúc có thể là một sự thay thế. Một chút thanh lịch với nội địa hóa tĩnh:

import Foundation

struct MyError: LocalizedError, Equatable {

   private var description: String!

   init(description: String) {
       self.description = description
   }

   var errorDescription: String? {
       return description
   }

   public static func ==(lhs: MyError, rhs: MyError) -> Bool {
       return lhs.description == rhs.description
   }
}

extension MyError {

   static let noConnection = MyError(description: NSLocalizedString("No internet connection",comment: ""))
   static let requestFailed = MyError(description: NSLocalizedString("Request failed",comment: ""))
}

func throwNoConnectionError() throws {
   throw MyError.noConnection
}

do {
   try throwNoConnectionError()
}
catch let myError as MyError {
   switch myError {
   case .noConnection:
       print("noConnection: \(myError.localizedDescription)")
   case .requestFailed:
       print("requestFailed: \(myError.localizedDescription)")
   default:
      print("default: \(myError.localizedDescription)")
   }
}

0

Đây là giải pháp thanh lịch hơn:

  enum ApiError: String, LocalizedError {

    case invalidCredentials = "Invalid credentials"
    case noConnection = "No connection"

    var localizedDescription: String { return NSLocalizedString(self.rawValue, comment: "") }

  }

4
Điều này có thể thanh lịch hơn trong thời gian chạy, nhưng bước bản địa hóa tĩnh sẽ không trích xuất các chuỗi này cho người dịch; bạn sẽ thấy một "Bad entry in file – Argument is not a literal string"lỗi khi bạn chạy exportLocalizationshoặc genstringsđể tạo danh sách văn bản có thể dịch của bạn.
savinola

@savinola đồng ý, nội địa hóa tĩnh sẽ không hoạt động trong trường hợp đó. Có lẽ sử dụng switch + casechỉ là tùy chọn ...
Vitaliy Gozhenko

Sử dụng các giá trị thô cũng sẽ ngăn việc sử dụng các giá trị liên quan cho bất kỳ lỗi nào của bạn
Brody Robertson
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.