Bagaimana cara memberikan deskripsi yang dilokalkan dengan tipe Galat di Swift?


203

Saya mendefinisikan jenis kesalahan khusus dengan sintaks Swift 3 dan saya ingin memberikan deskripsi ramah-pengguna dari kesalahan yang dikembalikan oleh localizedDescriptionproperti Errorobjek. Bagaimana saya bisa melakukannya?

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.)"

Apakah ada cara untuk localizedDescriptionmengembalikan deskripsi kesalahan khusus saya ("Deskripsi kesalahan yang mudah digunakan oleh pengguna.")? Perhatikan bahwa objek kesalahan di sini adalah tipe Errordan bukan MyError. Saya bisa, tentu saja, melemparkan objek ke MyError

(error as? MyError)?.localizedDescription

tetapi apakah ada cara untuk membuatnya bekerja tanpa melakukan casting ke tipe kesalahan saya?

Jawaban:


403

Seperti yang dijelaskan dalam catatan rilis Xcode 8 beta 6,

Jenis kesalahan yang ditentukan dengan cepat dapat memberikan deskripsi kesalahan yang dilokalkan dengan mengadopsi protokol LocalizedError yang baru.

Dalam kasus Anda:

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.

Anda dapat memberikan lebih banyak informasi jika kesalahan dikonversi ke NSError(yang selalu memungkinkan):

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.")

Dengan mengadopsi CustomNSErrorprotokol kesalahan dapat menyediakan userInfokamus (dan juga a domaindan code). Contoh:

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
Apakah ada alasan mengapa Anda membuat MyErroryang Errorpertama dan memperpanjangnya LocalizedErrornanti? Apakah ada perbedaan jika Anda membuatnya LocalizedErrordi tempat pertama?
Astaga.

9
@ Gee.E: Tidak ada bedanya. Ini hanya cara untuk mengatur kode (satu ekstensi untuk setiap protokol). Bandingkan stackoverflow.com/questions/36263892/… , stackoverflow.com/questions/40502086/… , atau natashatherobot.com/using-swift-extensions .
Martin R

4
Ah, periksa. Saya mengerti apa yang Anda katakan sekarang. Bagian "Protokol Kesesuaian" pada natashatherobot.com/using-swift-extensions memang contoh yang baik tentang apa yang Anda maksud. Terima kasih!
Astaga.

1
@ MartinR Jika kesalahan saya akan dikonversi ke NSError bagaimana saya bisa melewati kamus dari kesalahan yang dapat diakses sebagai userInfo NSError?
BangOperator

18
Hati-hati untuk mengetik var errorDescription: String?bukan String. Ada bug dalam implementasi LocalizedError. Lihat SR-5858 .
ethanhuang13

35

Saya juga akan menambahkan, jika kesalahan Anda memiliki parameter seperti ini

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

Anda dapat memanggil parameter ini dalam deskripsi lokal Anda seperti ini:

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

Anda bahkan dapat membuat ini lebih pendek seperti ini:

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

4

Sekarang ada dua protokol pengadopsi kesalahan yang dapat diadopsi oleh tipe kesalahan Anda untuk memberikan informasi tambahan kepada Objective-C - LocalizedError dan CustomNSError. Berikut ini contoh kesalahan yang mengadopsi keduanya:

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
Bisakah Anda mengedit? Contoh Anda tidak banyak membantu untuk memahami nilai masing-masing. Atau hapus saja karena jawaban MartinR menawarkan ini dengan tepat ...
Honey

3

Menggunakan struct dapat menjadi alternatif. Sedikit keanggunan dengan pelokalan statis:

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

Inilah solusi yang lebih elegan:

  enum ApiError: String, LocalizedError {

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

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

  }

4
Ini mungkin lebih elegan saat runtime, tetapi langkah pelokalan statis akan gagal mengekstraksi string ini untuk penerjemah; Anda akan melihat "Bad entry in file – Argument is not a literal string"kesalahan saat menjalankan exportLocalizationsatau genstringsmembuat daftar teks yang dapat diterjemahkan.
savinola

@savinola setuju, pelokalan statis tidak akan berfungsi jika demikian. Mungkin switch + casehanya menggunakan opsi ...
Vitaliy Gozhenko

Menggunakan nilai mentah juga akan mencegah penggunaan nilai terkait untuk kesalahan Anda
Brody Robertson
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.