prosource

Swift에서 사용자 지정 메시지로 오류/예외를 발생시키는 가장 간단한 방법은 무엇입니까?

probook 2023. 5. 18. 21:07
반응형

Swift에서 사용자 지정 메시지로 오류/예외를 발생시키는 가장 간단한 방법은 무엇입니까?

저는 스위프트에서 제가 다른 여러 언어로 하는 것에 익숙한 것을 하고 싶습니다: 사용자 지정 메시지와 함께 런타임 예외를 던집니다.예(Java):

throw new RuntimeException("A custom message here")

ErrorType 프로토콜을 준수하는 열거형을 던질 수는 있지만 모든 유형의 오류에 대해 열거형을 정의할 필요는 없습니다.이상적으로 위의 예를 가능한 한 가깝게 모방할 수 있으면 좋겠습니다.저는 ErrorType 프로토콜을 구현하는 사용자 지정 클래스를 만드는 것에 대해 알아봤지만, 그 프로토콜에 무엇이 필요한지조차 알 수 없습니다.아이디어?

가장 간단한 접근 방식은 하나의 사용자 정의입니다.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에부하에 Error:

extension String: Error {}

그런 다음 문자열을 던질 수 있습니다.

throw "Some Error"

를 자열자다같음이만것드는과체를로 만드는 것.localizedString의 대신 확수류장할있는오를 확장할 수 있습니다.LocalizedError:

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

@nick-keets의 솔루션은 가장 우아하지만, 테스트 대상에서 다음과 같은 컴파일 시간 오류로 인해 고장이 났습니다.

Redundant conformance of 'String' to protocol 'Error'

다음은 또 다른 접근 방식입니다.

struct RuntimeError: LocalizedError {
    let description: String

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

    var errorDescription: String? {
        description
    }
}

사용 방법:

throw RuntimeError("Error message.")

중요:사용하도록 업데이트됨LocalizedErrorError.사용.Error 및우위순선iding를 재정의함localizedDescription올바른 설명을 반환하지 않습니다.대신 다음과 같이 표시됩니다.The operation couldn’t be completed. (__lldb_expr_39.RuntimeError error 1.)

스위프트 4:

기준:

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

사용자 지정 예외를 정의하지 않으려면 다음과 같이 표준 NSError 개체를 사용할 수 있습니다.

import Foundation

do {
  throw NSError(domain: "my error domain", 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 domain, 42
    User info:
        key=ui1, value=12
        key=ui2, value=val2

이를 통해 사용자 지정 문자열(오류 도메인)과 더불어 숫자 코드 및 사전과 필요한 모든 추가 데이터를 모든 유형으로 제공할 수 있습니다.

N.B.: 이것은 OS= Linux(Ubuntu 16.04 LTS)에서 테스트되었습니다.

이 멋진 버전을 확인해 보세요.이 방법은 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
}

추가 확장자, 열거형, 클래스 등이 없는 가장 간단한 솔루션:

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

응용 "fatalError"를 .fatalError ("Custom message here")

@Nickkeets 답변을 기반으로 한 보다 완벽한 예는 다음과 같습니다.

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
}

원래는 제 swift 블로그에 게시되었습니다: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

먼저 몇 가지 사용 예를 본 다음 이러한 샘플을 작동시키는 방법(정의)을 살펴보겠습니다.

사용.

do {
    throw MyError.Failure
} catch {
    print(error.localizedDescription)
}

또는 보다 구체적인 스타일:

do {
    try somethingThatThrows()
} catch MyError.Failure {
    // Handle special case here.
} catch MyError.Rejected {
    // Another special case...
} catch {
    print(error.localizedDescription)
}

또한 다음과 같이 분류할 수 있습니다.

do {
    // ...
} catch is MyOtherErrorEnum {
    // If you handle entire category equally.
} catch let error as MyError {
    // Or handle few cases equally (without string-compare).
    switch error {
    case .Failure:
        fallthrough;
    case .Rejected:
        myShowErrorDialog(error);
    default:
        break
    }
}

정의.

public enum MyError: String, LocalizedError {
    case Failure = "Connection fail - double check internet access."
    case Rejected = "Invalid credentials, try again."
    case Unknown = "Unexpected REST-API error."

    public var errorDescription: String? { self.rawValue }
}

장단점

Swift 정의error변수는 자동으로 변경되며 핸들러는 읽기만 하면 됩니다.localizedDescription소유물.

하지만 그것은 모호하며, 우리는 "을 사용해야 합니다.catch MyError.Failure {}style 대신에 (우리가 취급하는 사례에 대해 명확히 하기 위해), 그러나 사용 예와 같이 범주화가 가능합니다.

  1. Teodor-Ciuraru의 답변(거의 동등함)은 여전히 긴 수동 캐스트가 필요합니다(예:catch let error as User.UserValidationError { ... }").

  2. 승인된 범주화-지수 접근법의 단점:

    • 그가 스스로 언급하는 것처럼 너무 모호해서 포수들은 비교가 필요할 수 있습니다.String메시지!?(정확한 오류를 알기 위해).
    • 동일한 것을 두 번 이상 던질 경우 복사/붙여넣기 메시지가 필요합니다!!
    • 또한, "와 같은 긴 문구도 필요합니다.catch MyError.runtimeError(let errorMessage) { ... }".
  3. NSException 접근법은 분류-에넘 접근법의 동일한 단점을 가지고 있습니다(단, 더 짧은 캐치 문단 제외). 또한 공장에서 생성하고 던지기 위한 방법을 사용하더라도 상당히 복잡합니다.

결론

이것은 단순히 사용함으로써 다른 기존 솔루션을 완성합니다.LocalizedError대신에Error누군가가 나처럼 다른 게시물을 읽는 것을 막을 수 있기를 바랍니다.

(나의 게으름은 때때로 나에게 많은 일을 야기합니다.)

테스트

import Foundation
import XCTest
@testable import MyApp

class MyErrorTest: XCTestCase {
    func testErrorDescription_beSameAfterThrow() {
        let obj = MyError.Rejected;
        let msg = "Invalid credentials, try again."
        XCTAssertEqual(obj.rawValue, msg);
        XCTAssertEqual(obj.localizedDescription, msg);
        do {
            throw obj;
        } catch {
            XCTAssertEqual(error.localizedDescription, msg);
        }
    }

    func testThrow_triggersCorrectCatch() {
        // Specific.
        var caught = "None"
        do {
            throw MyError.Rejected;
        } catch MyError.Failure {
            caught = "Failure"
        } catch MyError.Rejected {
            caught = "Successful reject"
        } catch {
            caught = "Default"
        }
        XCTAssertEqual(caught, "Successful reject");
    }
}

기타 도구:

#1 구현하는 경우errorDescription각자의enum번거로우시겠지만 다음과 같이 한 번만 구현하십시오.

extension RawRepresentable where RawValue == String, Self: LocalizedError {
    public var errorDescription: String? {
        return self.rawValue;
    }
}

위는 이미 확장된 열거형에만 논리를 추가합니다.LocalizedError(하지만 하나는 제거할 수 있습니다."Self: LocalizedError모든 문자열에 적용할 수 있도록 하기 위해 사용됩니다.

#2 만약 우리가 추가적인 맥락이 필요하다면요, 예를 들면.FileNotFound파일 경로가 연결되어 있습니까?그것은 제 다른 게시물을 보세요.

https://stackoverflow.com/a/70448052/8740349

기본적으로 복사 및 추가LocalizedErrorEnum위의 링크에서 프로젝트로 한 번 이동하고 필요한 횟수만큼 관련 열거형으로 재사용할 수 있습니다.

저는 @Alexander-Borisenko의 답변이 마음에 드는데, 현지화된 설명이 오류로 발견되었을 때 반환되지 않았습니다.대신 LocalizedError를 사용해야 할 것 같습니다.

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

자세한 내용은 이 답변을 참조하십시오.

일단을 LocalizedErrorEnum의 사용 예, 그런 다음 샘플을 작동시키는 방법(소스 코드 섹션).

사용.

do {
    let path = "/my/path/to/file.txt";
    throw MyErrorCategory.FileNotFound(
        atPath: path
    );
} catch {
    print(error.localizedDescription);
}

출력:

Failed to find file. {
  atPath: /my/path/to/file.txt
}

정의:

public enum MyErrorCategory: LocalizedErrorEnum {
    case FileNotFound(String = "Failed to find file.", atPath: String)
    case Connection(String = "Connection fail - double check internet access.")
}

번째 됩니다.LocalizedErrorEnum

필요한 기능

#1 먼저 복사/붙여넣기가 없는 메시지와 다음 기능이 있는 메시지를 원합니다.catch사례를 나열하지 서로 다른 사례 각, 사를나지예않서사례그룹류오다른로고열)enum복사/삭제할 필요 없이 매우 고유하며 각 열거형은 다른 그룹으로 간주될 수 있습니다.

#2 두 번째로, "FileNotFound"와 같은 일부 오류는 파일 경로(그러나 원시 값)와 같은 변수 컨텍스트/세부 정보를 가져야 합니다.enum인스턴스(instance) 할당을 지원하지 않으며 #1과는 달리 기본 제공 enum해결책이 아님).

#3 마지막으로, 전체를 잡는 것이 아니라 각각의 사건을 따로 잡을 수 있기를 바랍니다.struct 및/는class그때 하는switch에의 에.catch그리고 우리가 처리하지 않는 사건들을 잊어버리는 것을 피하고 싶습니다.

소스 코드

합니다.LocalizedErrorEnum아래에서 프로젝트로 한 번 이동하고 필요한 만큼 관련 열거형으로 재사용할 수 있습니다.

public protocol LocalizedErrorEnum: LocalizedError {
    var errorDescription: String? { get }
}

extension LocalizedErrorEnum {
    public var errorDescription: String? {
        if let current = Mirror(reflecting: self).children.first {
            let mirror = Mirror(reflecting: current.value);
            // Initial error description.
            let message = mirror.children.first?.value as? String
                ?? current.label ?? "Unknown-case";
            var context = "";
            // Iterate additional context.
            var i = 0;
            for associated in mirror.children {
                if i >= 1 {
                    if let text = associated.value as? String {
                        context += "\n  ";
                        if let label: String = associated.label {
                            context += "\(label): "
                        }
                        context += text;
                    }
                }
                i += 1;
            }
            return context.isEmpty ? message : (
                message + " {" + context + "\n}"
            );
        }
        return "\(self)";
    }
}

프로필에 언급된 바와 같이 Apache 2.0 라이센스 하에서 위의 코드를 사용하는 것도 허용됩니다(귀인할 필요가 없음).

오류가 있는 추가 컨텍스트 변수가 필요하지 않은 경우(또는 다른 접근 방식과의 비교를 위해) 다른 답변도 참조하십시오.

코드를 던지는 것은 오류 메시지가 최종 사용자에게 표시하기에 적합한지 아니면 개발자 디버깅에만 사용하기 위한 것인지를 분명히 해야 합니다.할 수 있음을 합니다.DisplayableError이를구다니를 입니다.LocalizedError의전

struct DisplayableError: Error, LocalizedError {
    let errorDescription: String?

    init(_ description: String) {
        errorDescription = description
    }
}

던지기 용도:

throw DisplayableError("Out of pixie dust.")

표시 용도:

let messageToDisplay = error.localizedDescription

제안된 솔루션의 일부 변형을 제안하고자 합니다.

public enum MyError: Error {
    var localizedDescription: String {
        get {
            switch(self) {
                case .network(let message, let code):
                    return "\(message) (\(code))"
                case .invalidInput(message: let message):
                    return message
            }
        }
    }
    case network(message: String, code: Int)
    case invalidInput(message: String)
}

좀 더 많은 작업을 수행해야 하지만 모든 분야에서 최고의 기능을 제공합니다.

  • 것은입니다.enum그래서 그것은 사용될 수 있습니다.switch진술.
  • 모든 오류는 동일한 유형의 오류에 대해서도 다른 오류일 수 있는 메시지로 생성되어야 합니다(확장되는 열거형과 달리).String)
  • 은 메를시제니다공합 합니다.localizedDescription모든 개발자들이 기대하는 것.

@pj-finnegan의 답변, 여러 사람의 의견, 그리고 수락된 답변의 각주를 반복하기 위해…

여기에 제시된 몇 가지 다른 답변을 선호합니다(베스트 프랙티스를 찾고 있는 경우).하지만 질문에 대답할 가장 간단한 방법은 브리지 유형을 사용하는 것입니다(iOS/macOS/…).NSError.

func myFunction(meNoLikey:Bool) throws {
    guard meNoLikey == false else {
        throw NSError(domain: "SubsystemOfMyApp", code: 99, userInfo: [NSLocalizedDescriptionKey: "My Message!"] )
    }
    // safe to carry on…
}

의미 있는 도메인 또는 코드를 사용할지 여부를 결정할 수 있습니다.userInfo 쇠열NSLocalizedDescriptionKey요청한 메시지를 전달하는 데 필요한 유일한 항목입니다.

ㅠㅠNSError.UserInfoKeyuserInfo에서 할 추가 할 수 .오류가 발생한 사용자에게 세부 정보를 제공하려는 경우 원하는 항목을 추가할 수도 있습니다.

언급URL : https://stackoverflow.com/questions/31443645/simplest-way-to-throw-an-error-exception-with-a-custom-message-in-swift

반응형