วิธีที่ง่ายที่สุดในการโยนข้อผิดพลาด / ข้อยกเว้นด้วยข้อความที่กำหนดเองใน Swift 2?


145

ฉันต้องการทำบางสิ่งใน Swift 2 ที่ฉันคุ้นเคยกับภาษาอื่น ๆ หลายภาษา: ทิ้งข้อยกเว้นรันไทม์ด้วยข้อความที่กำหนดเอง ตัวอย่างเช่น (ใน Java):

throw new RuntimeException("A custom message here")

ฉันเข้าใจว่าฉันสามารถโยน enum ประเภทที่สอดคล้องกับโปรโตคอล ErrorType ได้ แต่ฉันไม่ต้องการกำหนด enums สำหรับข้อผิดพลาดทุกประเภทที่ฉันโยน ตามหลักการแล้วฉันต้องการเลียนแบบตัวอย่างข้างต้นให้ใกล้เคียงที่สุด ฉันตรวจสอบการสร้างคลาสแบบกำหนดเองที่ใช้โปรโตคอล ErrorType แต่ฉันไม่สามารถเข้าใจได้ว่าโปรโตคอลนั้นต้องการอะไร (ดูเอกสารประกอบ ) ไอเดีย?


2
Swift 2 throw / catch ไม่ใช่ข้อยกเว้น
zaph

คำตอบ:


209

วิธีที่ง่ายที่สุดน่าจะเป็นที่จะกำหนดหนึ่งที่กำหนดเองenumที่มีเพียงหนึ่งcaseที่มีStringแนบมากับมัน:

enum MyError: ErrorType {
    case runtimeError(String)
}

หรืออย่าง Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

ตัวอย่างการใช้งานจะเป็นดังนี้:

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

หากคุณต้องการใช้Errorประเภทที่มีอยู่ประเภททั่วไปส่วนใหญ่จะเป็นNSErrorและคุณสามารถสร้างเมธอดจากโรงงานเพื่อสร้างและโยนด้วยข้อความที่กำหนดเอง


สวัสดีฉันรู้ว่าเป็นปีที่คุณโพสต์คำตอบนี้ แต่ฉันอยากทราบว่าเป็นไปได้หรือไม่ที่จะได้รับข้อมูลStringภายในของคุณerrorMessageถ้าเป็นเช่นนั้นฉันจะทำอย่างไร
Renan Camaforte

1
@RenanCamaforte ฉันขอโทษฉันไม่เข้าใจคำถาม? Stringมีการเชื่อมโยงที่นี่ด้วยMyError.RuntimeError(ตั้งค่าในเวลาที่throw) และคุณจะสามารถเข้าถึงได้ที่catch(กับlet errorMessage)
Arkku

1
คุณถูกขอวิธีแก้ปัญหาที่ง่ายที่สุด วิธีแก้ปัญหาเมื่อคุณสร้าง enums ฟังก์ชันและอื่น ๆ ที่กำหนดเองนั้นไม่ง่าย ฉันรู้อย่างน้อยก็วิธีหนึ่ง แต่ฉันจะไม่โพสต์ไว้ที่นั่นเพราะเป็นไปเพื่อวัตถุประสงค์ -C
Vyachaslav Gerchicov

3
@VyachaslavGerchicov หากคุณไม่ทราบวิธีที่ง่ายกว่าสำหรับ Swift ซึ่งระบุไว้ในคำถามด้วยเช่นกันนี่จะเป็นวิธีที่ง่ายที่สุดแม้ว่าคุณจะไม่คิดว่ามันง่ายในบริบททั่วไปที่จะรวมถึง Objective-C . (นอกจากนี้คำตอบนี้เป็นคำจำกัดความแบบครั้งเดียวแบบครั้งเดียวของ enum ฟังก์ชันและการเรียกใช้เป็นตัวอย่างของการใช้งานไม่ใช่ส่วนหนึ่งของโซลูชัน)
Arkku

1
@Otar ใช่ แต่ ... คุณกำลังพูดถึงtry!ซึ่งไม่ได้ใช้ที่นี่ tryแน่นอนคุณไม่สามารถแม้แต่จะทำให้การเรียกร้องที่อาจเกิดขึ้นโดยไม่ต้องขว้างชนิดของ (นอกจากนี้โค้ดส่วนนั้นยังเป็นตัวอย่างการใช้งานไม่ใช่วิธีแก้ปัญหาจริง)
Arkku

143

วิธีที่ง่ายที่สุดคือทำให้เป็นไปStringตามError:

extension String: Error {}

จากนั้นคุณสามารถโยนสตริง:

throw "Some Error"

หากต้องการทำให้สตริงเป็นlocalizedStringข้อผิดพลาดคุณสามารถขยายได้LocalizedError:

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

1
วิธีสวยหรูมาก!
Vitaliy Gozhenko

1
สวยหรูแน่นอน! แต่มันแบ่งเป้าหมายการทดสอบสำหรับฉันด้วยข้อความต่อไปนี้Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko

2
ด้วยเหตุผลบางประการสิ่งนี้ไม่ได้ผลสำหรับฉัน กล่าวว่าไม่สามารถดำเนินการให้เสร็จสมบูรณ์ได้เมื่อแยกวิเคราะห์error.localizedDescriptionหลังจากโยนสตริง
Noah Allen

2
คำเตือน: ส่วนขยายนี้ทำให้เกิดปัญหากับฉันกับไลบรารีภายนอก นี่คือตัวอย่างของฉัน สิ่งนี้เป็นไปได้สำหรับไลบรารีของบุคคลที่สามที่จัดการข้อผิดพลาด ฉันจะหลีกเลี่ยงส่วนขยายที่ทำให้ String สอดคล้องกับ Error
Bryan W. Wagner

1
โปรโตคอลควรประกาศว่า "เป็น" ประเภทใดไม่ใช่ประเภทที่ "อาจเป็น" สตริงไม่ใช่ข้อผิดพลาดเสมอไปและส่วนขยายนี้ทำให้ง่ายต่อการสันนิษฐานว่าเป็นการเอาชนะความปลอดภัยประเภท
dbplunkett

22

โซลูชันของ @ nick-keets นั้นสง่างามที่สุด แต่มันพังทลายลงสำหรับฉันในเป้าหมายการทดสอบด้วยข้อผิดพลาดเวลาคอมไพล์ต่อไปนี้:

Redundant conformance of 'String' to protocol 'Error'

นี่คือแนวทางอื่น:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

และที่จะใช้:

throw RuntimeError("Error message.")

19

Swift 4:

ตาม:

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

หากคุณไม่ต้องการกำหนดข้อยกเว้นที่กำหนดเองคุณสามารถใช้วัตถุ NSError มาตรฐานได้ดังนี้:

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

พิมพ์:

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

ซึ่งช่วยให้คุณสามารถระบุสตริงที่กำหนดเองรวมทั้งรหัสตัวเลขและพจนานุกรมพร้อมข้อมูลเพิ่มเติมทั้งหมดที่คุณต้องการไม่ว่าประเภทใดก็ได้

หมายเหตุ: สิ่งนี้ได้รับการทดสอบบน OS = Linux (Ubuntu 16.04 LTS)


19

ตรวจสอบเวอร์ชันที่ยอดเยี่ยมนี้ แนวคิดคือการใช้โปรโตคอล String และ ErrorType และใช้ rawValue ของข้อผิดพลาด

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

การใช้งาน:

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
}

ดูเหมือนว่าจะมีประโยชน์เพียงเล็กน้อยในแนวทางนี้เนื่องจากคุณยังต้องการas User.UserValidationErrorและยิ่ง.rawValueไปกว่านั้น แต่ถ้าคุณดำเนินการแทนCustomStringConvertibleเป็นvar description: String { return rawValue }มันอาจจะมีประโยชน์ที่จะได้รับรายละเอียดที่กำหนดเองโดยใช้ไวยากรณ์ enum โดยไม่ต้องผ่านไปrawValueในทุกสถานที่ที่คุณพิมพ์
Arkku

1
ใช้เมธอด localizedDescription ได้ดีขึ้นเพื่อส่งคืน .rawValue
DanSkeel

14

ทางออกที่ง่ายที่สุดโดยไม่ต้องมีส่วนขยายเพิ่มเติม enums คลาสและอื่น ๆ :

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

2
อีกครั้ง ความคิดเห็นของคุณเกี่ยวกับคำตอบของฉันนี่เป็นเรื่องง่ายในแง่ที่คุณได้ตัดสินใจโดยพลการแล้วว่าการกำหนดและ enum หรือส่วนขยายครั้งเดียวนั้นซับซ้อน ใช่คำตอบของคุณมี "การตั้งค่า" เป็นศูนย์ แต่ค่าใช้จ่ายของการมีข้อยกเว้นในการโยนทุกครั้งเป็นคาถาที่ซับซ้อนและไม่เหมือน Swiftlike ( raise()แทนthrow) ซึ่งยากที่จะจำ เปรียบเทียบวิธีการแก้ปัญหาของคุณด้วยthrow Foo.Bar("baz")หรือthrow "foo"คูณด้วยจำนวนของสถานที่ที่ยกเว้นจะโยน - IMO ค่าธรรมเนียมเพียงครั้งเดียวของการขยายหนึ่งบรรทัดหรือ enum NSExceptionNameอยู่ไกลกว่าที่จะสิ่งที่ต้องการ
Arkku

@Arkku ตัวอย่างเช่นpostNotificationต้องใช้ 2-3 params และตัวเลือกก็คล้ายกับอันนี้ คุณลบล้างNotificationและ / หรือNotificationCenterในแต่ละโปรเจ็กต์เพื่อให้ยอมรับพารามิเตอร์อินพุตน้อยลงหรือไม่?
Vyachaslav Gerchicov

1
ไม่และฉันจะไม่ใช้วิธีแก้ปัญหาในคำตอบของตัวเองด้วยซ้ำ ฉันโพสต์เพื่อตอบคำถามเท่านั้นไม่ใช่เพราะเป็นสิ่งที่ฉันทำเอง อย่างไรก็ตามนั่นคือนอกเหนือจากประเด็น: ฉันยืนตามความเห็นว่าคำตอบของคุณนั้นซับซ้อนกว่าที่จะใช้ของฉันหรือของ Nick Keets แน่นอนว่ามีประเด็นที่ถูกต้องอื่น ๆ ที่ต้องพิจารณาเช่นหากการขยายStringเพื่อให้สอดคล้องErrorนั้นน่าแปลกใจเกินไปหรือถ้าMyErrorenum คลุมเครือเกินไป (โดยส่วนตัวฉันจะตอบว่าใช่สำหรับทั้งสองอย่างและทำกรณี enum แยกต่างหากสำหรับข้อผิดพลาดแต่ละข้อเช่นthrow ThisTypeOfError.thisParticularCase).
Arkku

8

ในกรณีที่คุณไม่จำเป็นต้องจับข้อผิดพลาดและต้องการหยุดแอปพลิเคชันทันทีคุณสามารถใช้ fatalError: fatalError ("Custom message here")


3
โปรดทราบว่าสิ่งนี้จะไม่ทำให้เกิดข้อผิดพลาดที่สามารถจับได้ การดำเนินการนี้จะทำให้แอปขัดข้อง
Adil Hussain

6

จากคำตอบของ @Nick keets นี่คือตัวอย่างที่สมบูรณ์ยิ่งขึ้น:

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
}

เผยแพร่ครั้งแรกในบล็อกที่รวดเร็วของฉัน: http://eon.codes/blog/2017/09/01/throwing-simple-errors/


1
TBH: ตอนนี้ฉันเพิ่งทำthrow NSError(message: "err", code: 0)
eonist

คุณไม่ได้ใช้ตัวอย่างของคุณเอง? : D อ้อแล้วการโต้เถียงครั้งแรกควรdomainไม่ใช่messageใช่ไหม
NRitH

1
โดเมนที่ใช่ของคุณ และไม่ใส่น้ำตาลมากเกินไปในโค้ด ฉันมักจะสร้างกรอบและโมดูลขนาดเล็กจำนวนมากและพยายามทำให้น้ำตาลส่วนขยายที่สะดวกอยู่ในระดับต่ำ วันนี้ฉันพยายามใช้ส่วนผสมระหว่าง Result และ NSError
eonist

4

ฉันชอบคำตอบของ @ Alexander-Borisenko แต่คำอธิบายที่แปลเป็นภาษาท้องถิ่นจะไม่ส่งคืนเมื่อถูกจับว่าเป็นข้อผิดพลาด ดูเหมือนว่าคุณต้องใช้ LocalizedError แทน:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.