Cara termudah untuk melempar kesalahan / pengecualian dengan pesan khusus di Swift 2?

136

Saya ingin melakukan sesuatu di Swift 2 yang biasa saya lakukan dalam beberapa bahasa lain: melempar pengecualian runtime dengan pesan khusus. Misalnya (di Jawa):

throw new RuntimeException("A custom message here")

Saya mengerti bahwa saya dapat membuang tipe enum yang sesuai dengan protokol ErrorType, tetapi saya tidak ingin harus mendefinisikan enum untuk setiap jenis kesalahan yang saya lemparkan. Idealnya, saya ingin meniru contoh di atas sedekat mungkin. Saya melihat ke dalam membuat kelas kustom yang mengimplementasikan protokol ErrorType, tapi saya bahkan tidak tahu apa yang dibutuhkan oleh protokol itu (lihat dokumentasi ). Ide ide?

markdb314
sumber
2
Swift 2 throw / catch bukan pengecualian.
zaph

Jawaban:

194

Pendekatan yang paling sederhana mungkin untuk mendefinisikan satu kebiasaan enumdengan hanya satu caseyang telah Stringmelekat padanya:

enum MyError: ErrorType {
    case runtimeError(String)
}

Atau, pada Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Contoh penggunaannya akan seperti:

func someFunction() throws {
    throw MyError.runtimeError("some message")
}
do {
    try someFunction()
} catch MyError.runtimeError(let errorMessage) {
    print(errorMessage)
}

Jika Anda ingin menggunakan Errorjenis yang sudah ada , yang paling umum adalah NSError, dan Anda bisa membuat metode pabrik untuk membuat dan melempar dengan pesan khusus.

Arkku
sumber
Hai, saya tahu sudah setahun Anda memposting jawaban ini, tapi saya ingin tahu apakah mungkin untuk mendapatkan bagian Stringdalam Anda errorMessage, jika demikian, bagaimana saya melakukannya?
Renan Camaforte
1
@RenanCamaforte Maaf, saya tidak mengerti pertanyaannya? Di Stringsini dikaitkan dengan MyError.RuntimeError(diatur pada saat throw), dan Anda mendapatkan akses ke itu pada catch(dengan let errorMessage).
Arkku
1
Anda diminta solusi paling sederhana. Solusi saat Anda membuat enum, fungsi, dll. Tidak mudah. Saya tahu setidaknya satu cara tetapi saya tidak akan mempostingnya di sana karena itu untuk tujuan-C
Vyachaslav Gerchicov
3
@VyachaslavGerchicov Jika Anda tidak tahu cara yang lebih sederhana untuk Swift, yang juga ditentukan dalam pertanyaan, maka ini akan menjadi cara paling sederhana , bahkan dari Anda tidak menganggapnya sederhana dalam konteks yang lebih umum yang akan mencakup Objective-C . (Juga, jawaban ini pada dasarnya adalah definisi enum satu kali satu baris, fungsi dan panggilannya adalah contoh penggunaan, bukan bagian dari solusi.)
Arkku
1
@Otar Ya, tapi ... yang Anda bicarakan try!, yang tidak digunakan di sini. Anda bahkan tidak dapat melakukan panggilan yang berpotensi tanpa jenis try. (Juga bagian dari kode itu adalah contoh penggunaan, bukan solusi yang sebenarnya.)
Arkku
136

Cara paling sederhana adalah dengan Stringmenyesuaikan diri dengan Error:

extension String: Error {}

Maka Anda bisa melempar string:

throw "Some Error"

Untuk menjadikan string itu sendiri sebagai localizedStringgalat, Anda dapat memperluas LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Nick Keets
sumber
Ini pintar, tetapi adakah cara untuk membuatnya localizedDescriptionmenjadi senar itu sendiri?
villapossu
1
Cara yang sangat elegan!
Vitaliy Gozhenko
1
Memang elegan! Tapi itu memecah bagi saya dalam target uji dengan pesan berikut Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko
2
Untuk beberapa alasan ini tidak berhasil untuk saya. Mengatakan itu tidak dapat menyelesaikan operasi ketika parsing error.localizedDescriptionsetelah melempar string.
Noah Allen
1
Peringatan: ekstensi ini menyebabkan masalah bagi saya dengan perpustakaan eksternal. Inilah contoh saya . Ini dimungkinkan untuk perpustakaan pihak ketiga mana pun yang mengelola Kesalahan; Saya akan menghindari ekstensi yang membuat String sesuai dengan Kesalahan.
Bryan W. Wagner
20

@ nick-keets's solusi yang paling elegan, tapi itu gagal bagi saya dalam target pengujian dengan kesalahan waktu kompilasi berikut:

Redundant conformance of 'String' to protocol 'Error'

Inilah pendekatan lain:

struct RuntimeError: Error {
    let message: String

    init(_ message: String) {
        self.message = message
    }

    public var localizedDescription: String {
        return message
    }
}

Dan untuk menggunakan:

throw RuntimeError("Error message.")
Alexander Borisenko
sumber
19

Lihat versi keren ini. Idenya adalah untuk mengimplementasikan protokol String dan ErrorType dan menggunakan rawValue kesalahan.

enum UserValidationError: String, Error {
  case noFirstNameProvided = "Please insert your first name."
  case noLastNameProvided = "Please insert your last name."
  case noAgeProvided = "Please insert your age."
  case noEmailProvided = "Please insert your email."
}

Pemakaian:

do {
  try User.define(firstName,
                  lastName: lastName,
                  age: age,
                  email: email,
                  gender: gender,
                  location: location,
                  phone: phone)
}
catch let error as User.UserValidationError {
  print(error.rawValue)
  return
}
Teodor Ciuraru
sumber
Tampaknya ada sedikit manfaat dalam pendekatan ini, karena Anda masih memerlukan as User.UserValidationErrordan di atas itu .rawValue. Namun, jika Anda malah diimplementasikan CustomStringConvertiblesebagai var description: String { return rawValue }, hal ini mungkin berguna untuk mendapatkan deskripsi kustom menggunakan sintaks enum tanpa harus melalui rawValuedi setiap tempat di mana Anda mencetaknya.
Arkku
1
lebih baik menerapkan metode localizedDescription untuk mengembalikan .rawValue
DanSkeel
16

Swift 4:

Sesuai:

https://developer.apple.com/documentation/foundation/nserror

jika Anda tidak ingin mendefinisikan pengecualian khusus, Anda bisa menggunakan objek NSError standar sebagai berikut:

import Foundation

do {
  throw NSError(domain: "my error description", code: 42, userInfo: ["ui1":12, "ui2":"val2"] ) 
}
catch let error as NSError {
  print("Caught NSError: \(error.localizedDescription), \(error.domain), \(error.code)")
  let uis = error.userInfo 
  print("\tUser info:")
  for (key,value) in uis {
    print("\t\tkey=\(key), value=\(value)")
  }
}

Cetakan:

Caught NSError: The operation could not be completed, my error description, 42
    User info:
        key=ui1, value=12
        key=ui2, value=val2

Ini memungkinkan Anda untuk menyediakan string khusus, ditambah kode numerik dan kamus dengan semua data tambahan yang Anda butuhkan, apa pun jenisnya.

NB: ini diuji pada OS = Linux (Ubuntu 16.04 LTS).

PJ_Finnegan
sumber
12

Solusi paling sederhana tanpa ekstensi tambahan, enum, kelas, dan lain-lain .:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Vyachaslav Gerchicov
sumber
2
kembali. komentar Anda pada jawaban saya, ini sederhana hanya dalam arti bahwa Anda telah memutuskan secara sewenang-wenang bahwa mendefinisikan dan enum atau ekstensi sekali rumit. Jadi, ya, jawaban Anda memiliki nol baris "pengaturan", tetapi dengan biaya setiap pengecualian yang dilemparkan menjadi mantra yang rumit dan non-Swiftlike ( raise()bukan throw) yang sulit diingat. Bandingkan solusi Anda dengan throw Foo.Bar("baz")atau throw "foo"dikalikan dengan jumlah tempat di mana pengecualian dilemparkan - IMO biaya satu kali perpanjangan satu baris atau enum jauh lebih disukai daripada hal-hal seperti NSExceptionName.
Arkku
@Arkku Misalnya postNotificationmemerlukan 2-3 params dan pemilihnya mirip dengan yang ini. Apakah Anda mengesampingkan Notificationdan / atau NotificationCenterdalam setiap proyek untuk mengizinkannya menerima lebih sedikit input params?
Vyachaslav Gerchicov
1
Tidak, dan saya bahkan tidak akan menggunakan solusi dalam jawaban saya sendiri; Saya hanya mempostingnya untuk menjawab pertanyaan, bukan karena itu adalah sesuatu yang akan saya lakukan sendiri. Lagi pula, itu intinya: Saya mendukung pendapat bahwa jawaban Anda jauh lebih rumit untuk digunakan daripada jawaban saya atau jawaban Nick Keets. Tentu saja ada poin valid lain yang perlu dipertimbangkan, seperti jika memperpanjang Stringuntuk menyesuaikan diri Errorterlalu mengejutkan, atau jika MyErrorenum terlalu kabur (secara pribadi saya akan menjawab ya untuk keduanya, dan sebaliknya melakukan enum case terpisah untuk setiap kesalahan, yaitu, throw ThisTypeOfError.thisParticularCase).
Arkku
6

Berdasarkan jawaban @Nick keets, berikut adalah contoh yang lebih lengkap:

extension String: Error {} // Enables you to throw a string

extension String: LocalizedError { // Adds error.localizedDescription to Error instances
    public var errorDescription: String? { return self }
}

func test(color: NSColor) throws{
    if color == .red {
        throw "I don't like red"
    }else if color == .green {
        throw "I'm not into green"
    }else {
        throw "I like all other colors"
    }
}

do {
    try test(color: .green)
} catch let error where error.localizedDescription == "I don't like red"{
    Swift.print ("Error: \(error)") // "I don't like red"
}catch let error {
    Swift.print ("Other cases: Error: \(error.localizedDescription)") // I like all other colors
}

Awalnya diterbitkan di blog cepat saya: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

eonist
sumber
1
TBH: Sekarang saya lakukanthrow NSError(message: "err", code: 0)
eonist
Jadi Anda bahkan tidak menggunakan contoh Anda sendiri? : D Oh, dan argumen pertama seharusnya domain, bukan message, kan?
NRitH
1
Hak Anda, domain. Dan tidak, menambahkan terlalu banyak gula dalam kode. Saya biasanya membuat banyak kerangka kerja kecil dan modul dan mencoba untuk menjaga gula ekstensi nyaman rendah. Hari ini saya mencoba menggunakan campuran antara Hasil dan NSError
eonist
6

Jika Anda tidak perlu menangkap kesalahan dan Anda ingin segera menghentikan aplikasi, Anda dapat menggunakan fatalError: fatalError ("Custom message here")

Roney Sampaio
sumber
3
Perhatikan bahwa ini tidak akan menimbulkan kesalahan yang dapat ditangkap. Ini akan membuat aplikasi mogok.
Adil Hussain
4

Saya suka jawaban @ Alexander-Borisenko, tetapi deskripsi yang dilokalkan tidak dikembalikan ketika ditangkap sebagai Kesalahan. Tampaknya Anda perlu menggunakan LocalizedError sebagai gantinya:

struct RuntimeError: LocalizedError
{
    let message: String

    init(_ message: String)
    {
        self.message = message
    }

    public var errorDescription: String?
    {
        return message
    }
}

Lihat jawaban ini untuk lebih jelasnya.

Benjamin Smith
sumber