Simplest way to throw an error/exception with a custom message in Swift?

IosSwift

Ios Problem Overview


I want to do something in Swift that I'm used to doing in multiple other languages: throw a runtime exception with a custom message. For example (in Java):

throw new RuntimeException("A custom message here")

I understand that I can throw enum types that conform to the ErrorType protocol, but I don't want to have to define enums for every type of error I throw. Ideally, I'd like to be able mimic the example above as closely as possible. I looked into creating a custom class that implements the ErrorType protocol, but I can't even figure out that what that protocol requires. Ideas?

Ios Solutions


Solution 1 - Ios

The simplest approach is probably to define one custom enum with just one case that has a String attached to it:

enum MyError: ErrorType {
    case runtimeError(String)
}

Or, as of Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Example usage would be something like:

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

If you wish to use existing Error types, the most general one would be an NSError, and you could make a factory method to create and throw one with a custom message.

Solution 2 - Ios

The simplest way is to make String conform to Error:

extension String: Error {}

Then you can just throw a string:

throw "Some Error"

To make the string itself be the localizedString of the error you can instead extend LocalizedError:

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

Solution 3 - Ios

@nick-keets's solution is most elegant, but it did break down for me in test target with the following compile time error:

Redundant conformance of 'String' to protocol 'Error'

Here's another approach:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
	    return message
    }
}

And to use:

throw RuntimeError("Error message.")

Solution 4 - Ios

Swift 4:

As per:

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

if you don't want to define a custom exception, you could use a standard NSError object as follows:

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

Prints:

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

This allows you to provide a custom string (the error domain), plus a numeric code and a dictionary with all the additional data you need, of any type.

N.B.: this was tested on OS=Linux (Ubuntu 16.04 LTS).

Solution 5 - Ios

Check this cool version out. The idea is to implement both String and ErrorType protocols and use the error's 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."
}

Usage:

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
}

Solution 6 - Ios

Simplest solution without extra extensions, enums, classes and etc.:

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

Solution 7 - Ios

In case you don't need to catch the error and you want to immediately stop the application you can use a fatalError: fatalError ("Custom message here")

Solution 8 - Ios

Based on @Nick keets answer, here is a more complete example:

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
}

Originally published on my swift blog: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

Solution 9 - Ios

I like @Alexander-Borisenko's answer, but the localized description was not returned when caught as an Error. It seems that you need to use LocalizedError instead:

struct RuntimeError: LocalizedError
{
    let message: String
    
    init(_ message: String)
    {
        self.message = message
    }
    
    public var errorDescription: String?
    {
        return message
    }
}

See this answer for more details.

Solution 10 - Ios

First, let's see few usage examples, then how to make those samples work (Definition).

Usage

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

Or more specific style:

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

Also, categorization is possible:

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

Definition

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

Pros and Cons

Swift defines error variable automatically, and a handler only needs to read localizedDescription property.

But that is vague, and we should use "catch MyError.Failure {}" style instead (to be clear about what-case we handle), although, categorization is possible as shown in usage example.

  1. Teodor-Ciuraru's answer (which's almost equal) still needs a long manual cast (like "catch let error as User.UserValidationError { ... }").

  2. The accepted categorization-enum approach's disadvantages:

    • Is too vague as he comments himself, so that catchers may need to compare String message!? (just to know exact error).
    • For throwing same more than once, needs copy/pasting message!!
    • Also, needs a long phrase as well, like "catch MyError.runtimeError(let errorMessage) { ... }".
  3. The NSException approach has same disadvantages of categorization-enum approach (except maybe shorter catching paragraph), also, even if put in a factory method to create and throw, is quite complicated.

Conclusion

This completes other existing solutions, by simply using LocalizedError instead of Error, and hopefully saves someone from reading all other posts like me.

(My laziness sometimes causes me a lot of work.)

Testing

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

Other tools:

#1 If implementing errorDescription for each enum is a pain, then implement it once for all, like:

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

#2 What if we need additional context, like FileNotFound with file-path associated? see my other post for that:

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

>Basically, copy and add LocalizedErrorEnum from above link into your project once, and reuse as many times as required with associative-enums.

Solution 11 - Ios

Throwing code should make clear whether the error message is appropriate for display to end users or is only intended for developer debugging. To indicate a description is displayable to the user, I use a struct DisplayableError that implements the LocalizedError protocol.

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

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

Usage for throwing:

throw DisplayableError("Out of pixie dust.")

Usage for display:

let messageToDisplay = error.localizedDescription

Solution 12 - Ios

First, let's see LocalizedErrorEnum enum's usage examples, then how to make those samples work (in Sorce-code section).

Usage

Definition:

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

>The first argument is treated as message (in LocalizedErrorEnum enum).

Trigger:

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

Output:

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

Requirements (background)

#1 Firstly, I want messages without copy/pasting, and that with ability to catch a group of different error-cases, without listing each-case (solution, enum is pretty unique without copy/paste need, and each enum can be considered another group).

#2 Secondly, some errors like "FileNotFound" need to have variable context/details, like for file-path (but Raw-Value enum does not support instance-variables, and unlike #1, the built-in enum is not the solution).

#3 Lastly, I want to be able to catch each case separately, NOT catching entire struct and/or class then doing switch inside the catch, and want to avoid forgeting the rethrow of cases we don't handle.

Source-code (solution meeting requirements)

Simply, copy and add LocalizedErrorEnum from below into your project once, and reuse as many times as required with associative-enums.

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

>Note that as mentioned on my profile, reusing above code does not need attribution (and allows Apache 2.0 license). > >See also my other answer if you don't need additional context-variable with error (or for a comparison with other approaches).

Solution 13 - Ios

I would like to suggest a variation of some of the proposed solutions:

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

It's a little more work to create but it provides the best of all worlds:

  • It's an enum so it can be used in a switch statement.
  • All the errors must be created with a message that can be a different one even for the same types of error (unlike enums that extend String)
  • It provides the message under the localizedDescription that every developer is expecting.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionmarkdb314View Question on Stackoverflow
Solution 1 - IosArkkuView Answer on Stackoverflow
Solution 2 - IosNick KeetsView Answer on Stackoverflow
Solution 3 - IosAlexander BorisenkoView Answer on Stackoverflow
Solution 4 - IosPJ_FinneganView Answer on Stackoverflow
Solution 5 - IosTeodor CiuraruView Answer on Stackoverflow
Solution 6 - IosVyachaslav GerchicovView Answer on Stackoverflow
Solution 7 - IosRoney SampaioView Answer on Stackoverflow
Solution 8 - IosSentry.coView Answer on Stackoverflow
Solution 9 - IosBenjamin SmithView Answer on Stackoverflow
Solution 10 - IosTop-MasterView Answer on Stackoverflow
Solution 11 - IosEdward BreyView Answer on Stackoverflow
Solution 12 - IosTop-MasterView Answer on Stackoverflow
Solution 13 - IosSir CodesalotView Answer on Stackoverflow