Cách đơn giản nhất để đưa ra lỗi / ngoại lệ với thông báo tùy chỉnh trong Swift 2?


136

Tôi muốn làm một cái gì đó trong Swift 2 mà tôi đã từng làm bằng nhiều ngôn ngữ khác: ném ngoại lệ thời gian chạy với một thông báo tùy chỉnh. Ví dụ: (trong Java):

throw new RuntimeException("A custom message here")

Tôi hiểu rằng tôi có thể ném các loại enum phù hợp với giao thức ErrorType, nhưng tôi không muốn phải xác định enum cho mọi loại lỗi tôi ném. Lý tưởng nhất, tôi muốn có thể bắt chước ví dụ trên càng gần càng tốt. Tôi đã xem xét việc tạo một lớp tùy chỉnh thực hiện giao thức ErrorType, nhưng tôi thậm chí không thể hiểu được rằng giao thức đó yêu cầu gì (xem tài liệu ). Ý tưởng?


2
Swift 2 ném / bắt không phải là ngoại lệ.
zaph 16/07/2015

Câu trả lời:


194

Cách tiếp cận đơn giản nhất có lẽ là xác định một tùy chỉnh enumchỉ với một tùy chỉnh caseStringđính kèm:

enum MyError: ErrorType {
    case runtimeError(String)
}

Hoặc, kể từ Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Ví dụ sử dụng sẽ là một cái gì đó như:

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

Nếu bạn muốn sử dụng các Errorloại hiện có , loại chung nhất sẽ là một NSErrorvà bạn có thể tạo một phương thức xuất xưởng để tạo và ném một loại với một thông điệp tùy chỉnh.


Xin chào, tôi biết đó là một năm bạn đăng câu trả lời này, nhưng tôi muốn biết liệu có thể có được Stringbên trong của bạn không errorMessage, nếu vậy, làm thế nào để tôi làm điều đó?
Renan Camaforte

1
@RenanCamaforte Tôi xin lỗi, tôi không hiểu câu hỏi? Cái Stringnày được liên kết ở đây với MyError.RuntimeError(được đặt tại thời điểm throw) và bạn có quyền truy cập vào nó tại catch(với let errorMessage).
Arkku

1
Bạn đã được yêu cầu giải pháp đơn giản nhất. Giải pháp khi bạn tạo enums tùy chỉnh, hàm và vv không đơn giản. Tôi biết ít nhất một cách nhưng tôi sẽ không đăng nó ở đó vì nó là dành cho mục tiêu-C
Vyachaslav Gerchicov

3
@VyachaslavGerchicov Nếu bạn không biết một cách đơn giản hơn cho Swift, cũng được chỉ định trong câu hỏi, thì đây sẽ là cách đơn giản nhất, thậm chí bạn không coi nó đơn giản trong bối cảnh chung hơn bao gồm Objective-C . (Ngoài ra, câu trả lời này về cơ bản là định nghĩa một dòng một enum, hàm và lệnh gọi của nó là một ví dụ về cách sử dụng, không phải là một phần của giải pháp.)
Arkku

1
@Otar Có, nhưng bạn đang nói về try!, không được sử dụng ở đây. Bạn thực sự thậm chí không thể thực hiện cuộc gọi ném tiềm năng mà không có một số loại try. (Ngoài ra, một phần của mã là cách sử dụng ví dụ, không phải là giải pháp thực tế.)
Arkku

136

Cách đơn giản nhất là thực hiện Stringtheo Error:

extension String: Error {}

Sau đó, bạn có thể chỉ cần ném một chuỗi:

throw "Some Error"

Để làm cho chính chuỗi đó là localizedStringlỗi bạn có thể mở rộng LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}

Điều này là thông minh, nhưng có cách nào để biến nó localizedDescriptionthành chuỗi không?
Villapossu

1
Cách rất tao nhã!
Vitaliy Gozhenko

1
Thanh lịch thật! Nhưng nó đã phá vỡ đối với tôi trong các mục tiêu thử nghiệm với thông điệp sau Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko

2
Vì một số lý do, điều này không hiệu quả với tôi. Nói rằng nó không thể hoàn thành thao tác khi phân tích cú pháp error.localizedDescriptionsau khi ném một chuỗi.

1
Cảnh báo: tiện ích mở rộng này gây ra sự cố cho tôi với các thư viện bên ngoài. Đây là ví dụ của tôi . Điều này có thể cho bất kỳ thư viện bên thứ 3 nào quản lý lỗi; Tôi sẽ tránh các tiện ích mở rộng khiến String tuân thủ Lỗi.
Bryan W. Wagner

20

Giải pháp của @ nick-keets là thanh lịch nhất, nhưng nó đã bị hỏng trong mục tiêu thử nghiệm với lỗi thời gian biên dịch sau:

Redundant conformance of 'String' to protocol 'Error'

Đây là một cách tiếp cận khác:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

Và sử dụng:

throw RuntimeError("Error message.")

19

Kiểm tra phiên bản tuyệt vời này. Ý tưởng là để thực hiện cả giao thức String và ErrorType và sử dụng rawValue của lỗi.

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

Sử dụng:

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
}

Dường như có rất ít lợi ích trong phương pháp này, vì bạn vẫn cần as User.UserValidationErrorvà trên hết là .rawValue. Tuy nhiên, nếu bạn thay vì thực hiện CustomStringConvertiblenhư var description: String { return rawValue }, nó có thể có ích để có được giới thiệu tùy chỉnh bằng cách sử dụng cú pháp enum mà không cần phải đi qua rawValuetrong mọi nơi mà bạn in nó.
Arkku

1
triển khai tốt hơn phương thức mô tả cục bộ để trả về .rawValue
DanSkeel

16

Swift 4:

Theo:

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

nếu bạn không muốn xác định một ngoại lệ tùy chỉnh, bạn có thể sử dụng một đối tượng NSError tiêu chuẩn như sau:

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

Bản in:

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

Điều này cho phép bạn cung cấp một chuỗi tùy chỉnh, cộng với mã số và từ điển với tất cả dữ liệu bổ sung bạn cần, thuộc bất kỳ loại nào.

NB: điều này đã được thử nghiệm trên OS = Linux (Ubuntu 16.04 LTS).


12

Giải pháp đơn giản nhất mà không cần mở rộng thêm, enums, lớp và vv.:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()

2
tái Nhận xét của bạn về câu trả lời của tôi, điều này chỉ đơn giản theo nghĩa là bạn đã phần nào quyết định một cách tùy tiện rằng việc xác định và liệt kê hoặc mở rộng một lần là phức tạp. Vì vậy, vâng, câu trả lời của bạn không có dòng "thiết lập" nào, nhưng với chi phí có mỗi ngoại lệ bị ném là một câu thần chú phức tạp và không giống Swift ( raise()thay vì throw) rất khó nhớ. So sánh giải pháp của bạn với throw Foo.Bar("baz")hoặc throw "foo"nhân với số lượng địa điểm ném ngoại lệ - IMO phí một lần của tiện ích mở rộng một dòng hoặc enum được ưu tiên hơn nhiều so với những thứ như NSExceptionName.
Arkku

@Arkku Ví dụ postNotificationyêu cầu 2-3 params và bộ chọn của nó tương tự như cái này. Bạn có ghi đè Notificationvà / hoặc NotificationCentertrong mỗi dự án để cho phép nó chấp nhận các thông số đầu vào ít hơn không?
Vyachaslav Gerchicov

1
Không, và tôi thậm chí sẽ không sử dụng giải pháp trong câu trả lời của riêng mình; Tôi chỉ đăng nó để trả lời câu hỏi, không phải vì đó là việc tôi sẽ tự làm. Dù sao, đó là vấn đề bên cạnh: Tôi cho rằng câu trả lời của bạn phức tạp hơn nhiều so với sử dụng của tôi hoặc của Nick Keets. Tất nhiên, có những điểm hợp lệ khác để xem xét, chẳng hạn như nếu việc mở rộng Stringđể tuân thủ Errorlà quá đáng ngạc nhiên, hoặc nếu một MyErrorenum quá mơ hồ (cá nhân tôi sẽ trả lời có cho cả hai, và thay vào đó làm một trường hợp enum riêng cho mỗi lỗi, nghĩa là, throw ThisTypeOfError.thisParticularCase).
Arkku

6

Dựa trên câu trả lời của @Nick keets, đây là một ví dụ đầy đủ hơn:

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
}

Được xuất bản lần đầu trên blog nhanh chóng của tôi: http://eon.codes/blog/2017/09/01/throwing-simple-errors/


1
TBH: Bây giờ tôi chỉ cần làmthrow NSError(message: "err", code: 0)
eonist

Vì vậy, bạn thậm chí không sử dụng ví dụ của riêng bạn? : D Oh, và đối số đầu tiên nên domain, phải không message?
NRitH

1
Quyền của bạn, tên miền. Và không, thêm quá nhiều đường trong mã. Tôi thường tạo ra rất nhiều khung và mô-đun nhỏ và cố gắng giữ cho đường mở rộng thuận tiện ở mức thấp. Những ngày này, tôi cố gắng sử dụng kết hợp giữa Kết quả và NSError
eonist

6

Trong trường hợp bạn không cần phải bắt lỗi và bạn muốn dừng ngay ứng dụng, bạn có thể sử dụng fatalError: fatalError ("Custom message here")


3
Lưu ý rằng điều này sẽ không ném một lỗi có thể bị bắt. Điều này sẽ sụp đổ ứng dụng.
Adil Hussain

4

Tôi thích câu trả lời của @ Alexander-Borisenko, nhưng mô tả được bản địa hóa không được trả về khi bị bắt là Lỗi. Có vẻ như bạn cần sử dụng LocalizedError thay thế:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Xem câu trả lời này để biết thêm chi tiết.

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.