Skip to content

Instantly share code, notes, and snippets.

Revisions

  1. @nicklockwood nicklockwood revised this gist Aug 16, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Pattern for Swift Errors.swift
    Original file line number Diff line number Diff line change
    @@ -37,7 +37,7 @@ enum ApplicationError: Error, CustomStringConvertible {

    // Convenience constructor for converting any unknown error to an ApplicationError
    // this is useful when receiving errors where we're not sure what type they are,
    // which is more common that not gvien Swift's lack of Error type annotations
    // which is more common that not given Swift's lack of Error type annotations
    init(_ error: Error) {
    if let error = error as? ApplicationError {
    self = error
  2. @nicklockwood nicklockwood revised this gist Aug 16, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Pattern for Swift Errors.swift
    Original file line number Diff line number Diff line change
    @@ -22,7 +22,7 @@ enum ApplicationError: Error, CustomStringConvertible {
    case let message(message):
    return message
    case let generic(error):
    if let error = error as CustomStringConvertible {
    if let error = error as? CustomStringConvertible {
    return error.description
    }
    // Always returns something, but not always something useful
  3. @nicklockwood nicklockwood revised this gist Aug 16, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Pattern for Swift Errors.swift
    Original file line number Diff line number Diff line change
    @@ -42,7 +42,7 @@ enum ApplicationError: Error, CustomStringConvertible {
    if let error = error as? ApplicationError {
    self = error
    } else {
    self = .generic(message)
    self = .generic(error)
    }
    }

  4. @nicklockwood nicklockwood created this gist Aug 16, 2017.
    79 changes: 79 additions & 0 deletions Pattern for Swift Errors.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    // Swift's untyped errors are a goddam PiTA. Here's the pattern I use to try to work around this.
    // The goal is basically to try to guarantee that every throwing function in the app throws an
    // ApplicationError instead of some unknown error type. We can't actually enforce this statically
    // But by following this convention we can simplify error handling

    enum ApplicationError: Error, CustomStringConvertible {

    // These are application-specific errors that may need special treatment
    case specificError1
    case specificError2(SomeType)
    ...

    // These are generic cases for errors that don't need special treatment
    case message(String)
    case generic(Error)

    // Always handy to be able to print your errors
    var description: String {
    switch self {
    case .specificError1, .specificError2:
    // Application-specific
    case let message(message):
    return message
    case let generic(error):
    if let error = error as CustomStringConvertible {
    return error.description
    }
    // Always returns something, but not always something useful
    return (error as NSError).localizedDescription
    }
    }

    // Convenience constructor to save writing `ApplicationError.message(...)` all the time
    init(_ message: String) {
    self = .message(message)
    }

    // Convenience constructor for converting any unknown error to an ApplicationError
    // this is useful when receiving errors where we're not sure what type they are,
    // which is more common that not gvien Swift's lack of Error type annotations
    init(_ error: Error) {
    if let error = error as? ApplicationError {
    self = error
    } else {
    self = .generic(message)
    }
    }

    // By wrapping a call with this function, you can convert any thrown error to an ApplicationError
    // usage 1: `let result = try ApplicationError.wrap(someFunctionWithNoArguments)`
    // usage 2: `let result = try ApplicationError.wrap { try someFunction(with: arguments) }`
    static func wrap<T>(_ closure: () throws -> T) throws -> T {
    do {
    return try closure()
    } catch {
    throw self.init(error)
    }
    }

    // Like `wrap` above, but instead of calling the function and wrapping the error immediately,
    // this returns a new function that throws an ApplicationError instead of the original error
    // usage: let appErrorFn = ApplicationError.wrap(someUntypedErrorFn)
    static func wrap<T>(_ closure: @escaping () throws -> T) -> () throws -> T {
    return { try wrap(closure) }
    }

    // This function is basically an alternative version of try? that logs (or performs some other
    // application-specific action) instead of failing silently. This is useful if you need to call
    // a throwing function inside a function that doesn't throw, such as a delegate method
    static func attempt<T>(_ closure: () throws -> T) -> T? {
    do {
    return try closure()
    } catch {
    let error = ApplicationError(error)
    print(error.description) // Could do something more sophisticated, like store error in a global
    return nil
    }
    }
    }