在Swift 2中使用自定义消息抛出错误/异常的最简单方法?

时间:2015-07-16 00:50:03

标签: ios swift swift2

我想在Swift 2中做一些我曾经用多种其他语言做的事情:用自定义消息抛出运行​​时异常。例如(在Java中):

throw new RuntimeException("A custom message here")

我知道我可以抛出符合ErrorType协议的枚举类型,但我不想为我抛出的每种类型的错误定义枚举。理想情况下,我希望能够尽可能地模仿上面的例子。我研究了创建一个实现ErrorType协议的自定义类,但我甚至无法弄清楚该协议需要什么(参见documentation)。想法?

11 个答案:

答案 0 :(得分:160)

最简单的方法可能是定义一个自定义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,您可以创建一个工厂方法来创建并使用自定义消息抛出一个。

答案 1 :(得分:114)

最简单的方法是让String符合Error

extension String: Error {}

然后你可以抛出一个字符串:

throw "Some Error"

要使字符串本身成为错误的localizedString,您可以改为LocalizedError

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

答案 2 :(得分:15)

@ nick-keets的解决方案是最优雅的,但它确实在我的测试目标中打破了以下编译时错误:

<android.support.design.widget.CoordinatorLayout android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="#ececec"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#f7f7f7" android:fitsSystemWindows="true" android:minHeight="1dp" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed|enterAlwaysCollapsed"> <TextView android:layout_width="match_parent" android:layout_height="200dp" android:background="@color/color_3" android:layout_margin="10dp"/> <!--<include layout="@layout/wentixiangqing_dingbu_wentidaan"/>--> </android.support.design.widget.CollapsingToolbarLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#FFF"> <View android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"/> <FrameLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="star:123" android:textColor="#797979"/> <View android:layout_width="30dp" android:layout_height="3dp" android:layout_gravity="bottom|center_horizontal" android:background="#F00"/> </FrameLayout> <FrameLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginLeft="30dp" android:layout_marginRight="20dp" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="comment:90" android:textColor="#797979"/> <View android:layout_width="30dp" android:layout_height="3dp" android:layout_gravity="bottom|center_horizontal" android:background="#F00"/> </FrameLayout> </LinearLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="40dp" android:layout_marginTop="1dp" android:overScrollMode="always" /> </android.support.v4.widget.NestedScrollView> <Button android:layout_width="wrap_content" android:layout_height="40dp" android:layout_gravity="bottom|end" android:text="Comment" /></android.support.design.widget.CoordinatorLayout>

这是另一种方法:

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

答案 3 :(得分:13)

检查这个很酷的版本。我们的想法是实现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
}

答案 4 :(得分:12)

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

这允许您提供自定义字符串,以及包含任何类型所需的所有其他数据的数字代码和字典。

N.B。:这是在OS = Linux(Ubuntu 16.04 LTS)上测试的。

答案 5 :(得分:4)

基于@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/

答案 6 :(得分:4)

最简单的解决方案,没有额外的扩展,枚举,类等等:

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

答案 7 :(得分:2)

我喜欢@ Alexander-Borisenko的答案,但是当被捕获为错误时,不会返回本地化的描述。看来您需要改用LocalizedError:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

有关更多详细信息,请参见this answer

答案 8 :(得分:1)

首先,让我们看几个例子,然后看看如何实现。

用法

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:
    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 {} 样式(以明确我们处理的情况),尽管如用法示例所示,分类是可能的。

  1. Teodor-Ciuraru's almost equal answer 仍然需要长时间的手动转换(如 catch let error as User.UserValidationError { ... })。

  2. accepted categorization-enum approach 的缺点:

    • 他自己的评论太含糊了,以至于捕手可能需要比较String消息!? (只是为了知道确切的错误)。
    • 要多次抛出相同的消息,需要复制/粘贴消息!!
    • 此外,还需要一个长短语,例如 catch MyError.runtimeError(let errorMessage) { ... }
  3. NSException approach 具有与分类枚举方法相同的缺点(除了可能较短的捕获段落),而且,即使放入工厂方法来创建和抛出,也非常复杂。

结论

这完善了现有的解决方案,使用 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");
    }
}

答案 9 :(得分:0)

只需使用致命错误:     fatalError ("Custom message here")

答案 10 :(得分: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