6. Errors

In Swift, as in any other programming language, things fail. In daily development tasks, you encounter both logical errors—that is, things that compile but don’t work the way you expect them to—and runtime errors—errors that arise from real-world conditions such as missing resources or inaccessible services. Swift 2’s redesigned error-handling system enables you to respond to both kinds of error conditions. Its response mechanisms range from assertions that fail fatally to error types that support recovery, enabling you to track what went wrong and offer runtime workarounds.

The updated error system takes into account Apple’s vast Cocoa/Cocoa Touch ecosystem. Cocoa has a particular way of working. APIs return a usable value or some fail sentinel, such as false or nil. There’s often an error parameter that’s simultaneously populated. At each step, you check whether a call has failed. If so, you print an error message and return early from the method or function.

Objective-C and its flexible type system support this traditional Cocoa paradigm where code follows a smooth linear path of calls, tests, and returns. Swift, in contrast, offers a poor fit to this approach because of its higher safety standards. Type safety doesn’t marry easily into return polymorphism and side effects. The Swift 2 language update addressed these issues, offering greater safety and reliability in its redesigned error-handling system.

Failing Hard

When your app recognizes programmer errors, your code should fail loudly, forcibly, and noticeably. These errors include conditions that should never happen and reflect serious flaws in overall logic. Swift offers several ways to prematurely terminate an application. Its constructs help assure overall program correctness, test for outlier cases, and mandate conditions that must be true for your application to perform properly.

Fatal Errors

When you need your app to terminate, look no further than fatalError. The fatalError function unconditionally prints a message and stops execution. Use this function to exit your application after printing a note about why the app has stopped working. For example, you might encounter an inconsistent state:

fatalError("Flag value is zero. This should never happen. Goodbye!")

Or you might meet a requirement from an abstract superclass:

fatalError("Subclasses must override this method")

The failure reason is optional but strongly recommended, especially for any app that will ever be used by another human being—or even future-you. The compiler allows you to omit the error, like this, but common sense suggests that this isn’t such a good plan:

fatalError()

“It just quit” isn’t likely to improve anyone’s day, including yours.

Assertions

Use the assert function to establish a traditional C-style assertion with an optional message. Your assertions test assumptions and help locate design errors during development. As with fatalError, an explanatory message is optional but recommended:

assert(index < self.count)
assert(index < self.count, "Index is out of bounds")

The details of the compiler flags used to control evaluation are specified in the standard library module interface. False assertions stop execution to a debuggable state on debug builds (typically -Onone). For release builds, an assertion is entirely ignored, and the assertion’s condition clause is not executed.

To use checks in release builds, substitute the related precondition function, which is described in the following section. A precondition’s condition clause won’t execute in –Ounchecked builds.

As a rule, the simpler the assertion, the more valuable it tends to be. An assertion should mandate conditions you know to be true and necessary for proper execution. Assertions simplify debugging by ruling out conditions you know to be true, ensuring that failures occur for more exotic reasons. Here’s a trivial example of checking for valid values:

assert(1...20 ~= value, "Power level out of range. (Must be between 1 and 20.)")

Or you might test that a string contains some text:

assert(!string.isEmpty, "Empty string is invalid")

The assertionFailure variation does not use a predicate test; it always triggers as a false assertion. assertionFailure marks code that should never be reached and forces applications to terminate immediately:

assertionFailure()
assertionFailure("Not implemented")
assertionFailure("Switch state should never reach default case")

The assertionFailure function follows the same evaluation pattern as assert. It is ignored in release builds and enters a debuggable state for debug builds.

You can easily build a non-terminating version of assert that mimics its predicate testing and reporting behavior with less drastic outcomes than an application exit:

public func simpleAssert(
    @autoclosure condition:  () -> Bool,
    _ message: String) {
    if !condition() {print(message)}
}

To differentiate between debug and release builds, add the #if DEBUG trick described in Chapter 2, “Printing and Mirroring.” Since Swift does not automatically support a check for debug/release conditions, you must add a custom –D DEBUG compiler flag to your debug build settings, as described in that chapter. You might use this check, for example, to stop an application when debugging and throw errors in release builds.


Note

Avoid using conditions for side effects.


Preconditions

There’s no great philosophical benefit in allowing code to execute in production builds that you would not permit in debug ones. Unlike assertions, precondition and preconditionFailure calls are checked and will stop an app in release mode except when compiled with –Ounchecked. Of course, you try not to ship flawed code in the first place, but an out-of-bounds index will not magically fix itself in either situation. Weigh early termination against possible loss and corruption of user data.

For somewhat obvious reasons, use preconditions sparingly. With great failure comes great responsibility—and not-so-great one-star reviews. Also, with unexpected app termination comes another opportunity for your iTunes Connect App Store reviewer to spin the Wheel of Rejection. Here are several examples of how you’d use precondition calls with and without feedback messages:

precondition(index < self.count)
precondition(index < self.count, "Index is out of bounds")
preconditionFailure()
preconditionFailure("Switch state should never reach default case")

Precondition fails stop to a debuggable state in debug builds. They simply end execution in release builds.


Note

When compiling without optimization, preconditionFailure() emits the same code as fatalError(). Under optimization, fatalError() emits the same call, but preconditionFailure() ends up as a trap instruction.


Aborting and Exiting

Part of the Darwin library and most commonly used for command-line apps, abort produces simple abnormal process termination:

@noreturn func abort()

Use exit for traditional exit code support, which can then be tested in shell scripts for success and fail conditions:

@noreturn func exit(_: Int32)

exit causes normal process termination. The status value passed to the exit function returns to the parent, where it can then be evaluated. The C standard uses EXIT_SUCCESS and EXIT_FAILURE constants, both of which are available through Darwin.

Failing Gracefully

Applications often encounter runtime conditions that arise from conditions such as bad user input, network outage, or a file no longer existing. Despite errors, you’ll want ways to recover and continue normal execution rather than crash. To address this, Swift offers support for constructing, throwing, catching, and propagating errors at runtime.

Unlike many other programming languages, Swift does not use exceptions. Although Swift errors may resemble exception handlers (with its try, catch, and throw keywords), Swift does not unwind the call stack. Its errors are computationally efficient. A Swift throw statement resembles a return statement in terms of overhead.

These keywords may superficially remind you of exceptions, but they represent a distinct technology. As developer guru Mike Ash puts it, “Swift looks just enough like other languages (outside of Rust or Haskell or other hipster languages) to make you think you can get a head start but it’s different enough that little of your prior knowledge actually applies.” Swift may quack like an exception-producing duck, but don’t hold your breath waiting for eggs and a down vest.

The ErrorType Protocol

An error describes the conditions under which an operation failed. The traditional Cocoa error class is NSError. Its properties include a string-based error domain, a numeric code, and a localized dictionary of information to support issue reporting. Although the original design intended errors to be presented directly to users, developers are the primary consumer of NSError instances and their Swift descendants. Swift strips down error features to provide NSError interoperation support at the most minimal level.

A Swift error conforms to the ErrorType protocol. Although this is a public protocol, the standard library reveals nothing publicly about how the protocol works or is implemented. These are the official module declarations for this protocol:

public protocol ErrorType {
}

extension ErrorType {
}

Any Swift type—enumeration, class, or structure—supports ErrorType by declaring protocol conformance, as you see in the following examples. Internally, ErrorType implements two key NSError features: the string domain (_domain) and the numeric code (_code). These features provide compatibility with NSError. This quick peek at private implementation details is not intended for use in any App Store product:

class ErrorClass: ErrorType{}
ErrorClass()._code // 1
ErrorClass()._domain // "ErrorClass", the type name

struct ErrorStruct: ErrorType{}
ErrorStruct()._code // 1
ErrorStruct()._domain // "ErrorStruct"

enum ErrorEnum: ErrorType {case First, Second, Third}
ErrorEnum.First._code // 0, the enum descriminant
ErrorEnum.Second._code // 1
ErrorEnum.Third._code // 2
ErrorEnum.First._domain // "ErrorEnum"

Conforming to ErrorType enables any Swift construct to be NSError compatible. This applies regardless of whether the construct is a class. For example, you can cast a structure with ErrorStruct() as NSError to produce a minimal error populated with a code and domain:

print(ErrorStruct() as NSError)
    // Error Domain=ErrorStruct Code=1 "(null)"

The resulting error discards all custom state information you have added.

The "(null)" description usually describes material in the NSError’s userInfo dictionary, but at this time there is no way to populate that dictionary via casting. Apple’s Joe Groff wrote on Twittter, “It isn’t possible yet to control the userInfo or localized messages of a bridged NSError.”

Choosing Between Optionals and Error Handling

Many current APIs, especially those based on Core Foundation calls, have yet to be audited and transitioned to the new error-handling system. In such cases, you’re stuck using the old design. Make sure to always check your result for nil, 0, NULL or another signal before attempting to access an NSError instance.

When crafting new methods, consider how their results are used and whether you’re communicating an error or returning a true some-or-none situation. Not all nil-returning methods equate to errors. For example, consider optional chaining. Chaining ideally creates a parsimonious and readable set of operations that flow smoothly from one call to the next. Using optionals enables that chain to naturally break, albeit at the cost of not knowing why that break happened. When a method doesn’t require explicit error conditions, you can enable chaining by choosing optionals over errors.

Use optionals whenever your code explicitly handles a nil case. If the nil case is never of interest to the client code, error handling means you never have to unwrap.

As a rule, avoid using nil as a signal to indicate error states. Don’t pass-and-populate intermediate error structures as side effects. Throwing an error enables a caller to implement a recovery scheme that works around a failure scenario. Errors also enable you to represent and differentiate between distinct conditions and to better handle situations where you’d normally leave a scope and report an error.

Don’t forget that returning optionals and throwing errors are not mutually exclusive. An API can use optionals and errors to report three states: “request succeeded and returned value,” “request succeeded without returning value,” and “request had an error.”


Note

Swift’s try? command bridges between error handling and optionals, enabling API consumers to convert throwing calls into optional chaining. This approach discards error details, establishing try? as the ugly offspring of error:nil calls.


Swift Error Rules

Swift error handling offers ways to respond to and recover from application error conditions. You need only follow a few simple rules to be a good citizen under the current system. This section overviews those rules and discusses how to best follow them in your applications.

Rule 1: Move Away from nil Sentinels

Until Swift 2, failable methods would take an in-out error parameter or error pointer and return an optional value. Here’s an example from NSAttributedString, pre-Swift 2:

func dataFromRange(range: NSRange,
    documentAttributes dict: [NSObject : AnyObject],
    error: NSErrorPointer) -> NSData?

In this older approach, you tested the returned optional value. If the method didn’t succeed and would return nil, the method populated its error, which in turn might then be passed back through successive calls to some originating function. At each stage, a calling function might report an error, discard an error, or create and populate an error. The following example shows a fairly common approach for nil-sentinel use:

if let resultOfOptionalType = failableOperation(params, &error) {
    // use result
} else {
    // report error
}

Until now, there were best practices but no fixed rules on how this was implemented or even if this was done properly. You might be a confident, reliable coder, but could you place equal trust in the coders of the APIs you called?

Worse, this approach encouraged repetitive error-prone code that lent itself to duplicated logic, excessive nesting, and late return. nil-sentinel calls that used error-populating side effects allowed you to bypass or ignore returned errors and didn’t offer a sufficiently reliable pathway from the source of a failed operation to the consumer of that issue. Swift 2 was designed to avoid these issues.

The updated error system offers a new pathway for errors. In the revised system, errors are considered at the point where they’re generated and at the point where they’re consumed. You no longer pass errors directly, avoiding those situations where you might have erroneously substituted the wrong error parameter or accidentally passed nil. Instead, you throw an error to the runtime system and try and/or catch it where you’ll use it. The throws keyword annotates any method that supports the new system.

Consider the updated API for the NSAttributedString function that started this section. This function gains the throws keyword, the NSData? return value is no longer optional, and the NSError argument has entirely disappeared:

// New
func dataFromRange(range: NSRange,
    documentAttributes dict: [String : AnyObject]) throws -> NSData

Where possible, your failable functions should adopt this standard and replace optionals-as-sentinels in favor of throwing functions that return non-optional values. This migration involves nontrivial work, as you see in the following steps, which describe the tasks you must perform:

1. Add the throws keyword after the argument list.

2. If the return type was used as a sentinel, change the return type from optional to non-optional by removing the question mark.

3. If needed, create an error type that represents the possible error states your type may encounter.

4. Replace any return nil call that was meant to report errors with throw statements, using your custom error types.

5. Audit your calling points:

Image If you do not care about error information, add try? to your calls. This approach enables you to largely retain your preconversion consumer code as try? calls hook into the new error mechanism but return optionals. Of course, ignoring errors isn’t generally a good idea; APIs report errors for good reasons.

Image If you’re guaranteed to return successful results without thrown errors, use try! and remove your optional-handling code like guard and if-let. This approach is dangerous. Any thrown errors result in application crashes.

Image If you want to handle the error, replace guard, if-let, and other optional-handling constructs with try and embed those calls in do-catch. This preferred approach enables you to differentiate errors and provide runtime mitigation. It also requires the most refactoring work.

Rule 2: Use throw to Raise Errors

When your app cannot continue its normal flow of execution, throw an error at the point where the issue occurs:

if some-task-has-failed {throw error-instance}

Throwing an error leaves the current scope (after executing all pending defer blocks) and invokes Swift’s error-handling system. That system enables API consumers to down the line to report the error, to handle it (using do-catch or as an optional with try?), and to attempt workarounds.

An error conforms to the ErrorType protocol, but there are essentially no other restrictions with regard to what that error looks like or how you implement it. While you are more than welcome to construct NSError instances, many Swift developers prefer to use Swift-native ErrorType constructs that better represent and communicate issues. For example, Swift enumerations provide a great way to group related errors together:

enum MyErrorEnumeration: ErrorType {case FirstError, SecondError, ...}

You might create an error enumeration related to authentication. In the following example, one of the cases uses an associated value to indicate a minimum retry period:

enum AuthenticationError : ErrorType {
    case InvalidUserCredentials
    case LoginPortalNotEnabled
    case PortalTimeOut(Int)
}

All standard Swift features from associated values to type methods to protocols are available to your errors. You’re not sending abstract signals; you’re throwing concrete type instances. Swift does not limit you to just printing your errors and returning prematurely from method calls, although you are certainly able to do that if your app does not need more complex functionality. Context-rich features enable you to catch and consume error instances that help you recover from failure and offer workarounds to your users.


Note

Internally, Swift implements any function that throws with an extra parameter. That parameter points to an error-storage slot that the caller places on the stack. On return, after calling with try, the caller checks whether a value has been placed in that slot.


Rule 3: Use Error Types with Visible Access

The error types you use can be declared anywhere, as long as they are visible to the points where they’re thrown and caught. If your errors are class specific, you can incorporate them as nested types directly into the class they support. Use access control modifiers to ensure appropriate visibility.

In the following example, the AuthenticationError type is nested within AuthenticationClass. Its public modifier ensures that the error can be seen, consumed, and used by any client of the parent class, even items external to the current module:

public class AuthenticationClass {
    public enum AuthenticationError : ErrorType {
        case InvalidUserCredentials
        case LoginPortalNotEnabled
        case PortalTimeOut(Int)
    }

    public func performAuthentication() throws {
        // ... execute authentication tasks ...
        // ... now something has gone wrong ...
        throw AuthenticationError.InvalidUserCredentials
    }
}

Rule 4: Mark All Error-Participating Methods with throws

Annotate throwing methods with the throws keyword. You place the keyword just after the parameter list and before any return type:

func myFailableMethod() throws -> ReturnType {...}

Mark any method that directly throws an error. You might not initially consider that you must also mark methods that use try but do not themselves consume errors. So, for example, if your method does not catch or otherwise handle the error, it must use the throws keyword:

func myMethodThatCallsTry() throws {
    try someFailableMethod()
}

When your method uses do-catch, try!, or try?, each of which can provide an endpoint for an error, you do not use throws unless, of course, the method itself throws further errors or does not consume all possible errors. The following method does not need a throws annotation:

func myMethodThatCatches() {
    do {
        try someFailableMethod()
    } catch {
        // handle error
    }
}

but this one does:

func myMethodThatCatches() throws {
    do {
        try someFailableMethod()
    } catch MyErrorType.OnlyOneKindOfError {
        // handle just this kind of error
        // but other errors may fall through
    }
}

Rule 5: Use rethrows Consistently

The rethrows keyword refers to a method that executes a closure that can itself throw. When you call that closure with try, the closure throws and the parent rethrows any errors:

func myRethrowingMethod(closure: () throws -> Void) rethrows {
    try closure()
}

When working with protocols, a throws method cannot override a rethrows member (although you can satisfy a throwing member with a rethrowing one). Use rethrows to consistently indicate a two-stage error-handling process and avoid potential compiler errors. If your function takes a closure as an argument and you want to allow the argument closure to throw (but your function does not itself actually throw anything or call any APIs that throw besides the argument closure), use rethrows.

Rule 6: Consume Errors Where They Matter

At some point along the error chain, a method may sufficiently care about a potentially failable task to take responsibility for an error. In such circumstances, your code has lots of flexibility. It can consume the error entirely. It may perform partial mitigation before turning around and continuing the error chain. It may wrap the error into a higher-level error rather than leak the error from underlying APIs. If continuing the chain, mark the method with throws. Even when a method consumes an error, it can throw one as well:

func myPartiallyMitigatingMethod() throws {
    do {
        try someFailableMethod()
    } catch {
        // perform mitigation tasks ...
        // then continue error chain
        throw error
    }
}

Rule 7: Terminate Threaded Error Chains

When working with asynchronous code, provide a natural endpoint for any throwing methods. Many asynchronous Cocoa calls use completion handlers. You place a request, and the completion block executes when the request completes, regardless of whether that request succeeded or failed.

You don’t try these asynchronous calls. There’s no point as you’re usually passing a handler that understands and manages error states. Instead, you work with returned values in the handler, which is already designed for this. A typical handler provides a data and error argument parameter tuple, as in this Social framework example:

slrequest.performRequestWithHandler {
    (NSData!, NSHTTPURLResponse!, NSError!) -> Void in
    // code handling
}

You test the data argument, and if its value is set to a nil sentinel of some kind, you handle error mitigation. Otherwise, you process the data. This offers a natural fit for if-let handling.

Swift’s redesigned error system doesn’t affect the externals of this call. There’s nothing to catch from this request. You do not have to call it with try. Inside the handler is another matter. The following example places an asynchronous request. It converts returned data to a string and saves that string to a file:

slrequest.performRequestWithHandler {
    (data: NSData!, response: NSHTTPURLResponse!, error: NSError!) -> Void in
    if let string = String(data: data, encoding: NSUTF8StringEncoding) {
        try string.writeToFile("/tmp/test.txt",
            atomically: true,
            encoding: NSUTF8StringEncoding)
    }
}

Because the writeToFile:atomically:encoding: function throws, you call it using some variant of try. This sets up an error-handling microenvironment within the handler closure.

At this point, you encounter an issue. The compiler complains about an error, specifically:

invalid conversion from throwing function of type '(NSData!, NSHTTPURLResponse!,
NSError!) throws -> Void' to non-throwing function type '@convention(block) (NSData!,
NSHTTPURLResponse!, NSError!) -> Void'

Adding try to the handler closure converts that closure to a throwing type. There’s no exhaustive catch or try? or try!, so it’s possible that an unhandled error could propagate further. The performRequestWithHandler parameter doesn’t accept throwing closures. To mitigate this issue, return the closure to a non-throwing version.

You do this by consuming thrown errors within the block. Use any of the myriad of Swift solutions, such as do-catch or if-let-try?. Exhaustively consuming errors converts the throwing closure to a non-throwing version and enables it to be passed as the handler parameter. By providing a full pathway for the errors to follow, you ensure that each potential error reaches an endpoint.

You see a similar issue when working with Grand Central Dispatch (GCD). Like the Social request, GCD executes a closure asynchronously. It is not set up to accept one whose signature includes throwing. In the following example, an outer function dispatches a block that throws:

public func dispatch_after(delay: NSTimeInterval, block: () throws -> Void) {
    // Construct error-handling equivalent dispatch
    dispatch_after(

        // build time offset
        dispatch_time(DISPATCH_TIME_NOW,
            Int64(delay * NSTimeInterval(NSEC_PER_SEC))),

        // "It is recommended to use quality of service class values to
        // identify the well-known global concurrent queues."
        dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),

        // Integrate error-throwing block into self-contained
        // do-catch error-handling structure
        {
            do {
                try block()
            }
            catch {
                print("Error during async execution")
                print(error)
            }
        }
    )
}

To succeed, this example must try the block and catch any errors. An exhaustive catch prevents those errors from propagating further and transforms the inner closure into one without a throws signature.

Without this overhead, the internal dispatch_after call won’t compile. It expects a non-throwing dispatch_block_t argument that cannot be provided if the closure contains any remaining error pathways.


Note

You can easily adapt this code to accept an error handler and/or completion handler to extend the functionality and flexibility of this approach.


Building Errors

To raise an error, throw any instance that conforms to ErrorType. In the simplest case, create a struct and throw it, as in the following example:

public struct SomethingWentWrong : ErrorType {}
...something happens...
throw SomethingWentWrong()

In the best of all possible worlds, your diagnostics should read less like random fortune cookie paper slips and more like constructive pointers that explain your failure. For example, avoid:

public enum SomethingWentWrongError: ErrorType {
    case YouWillFindNewLove
    case AClosedMouthGathersNoFeet
    case CynicsAreFrustratedOptimists
    case WhenEverythingIsComingYourWayYouAreInTheWrongLane
}

Good Errors

Errors exist at the intersection of information and flow control. Good errors should be written for human consumption in a professional environment. The following points discuss how you might best create error content for developer consumption:

Image Be clear. A good error message should establish what the issue is, the cause of the issue, the source of the issue, and how to resolve the issue. Take inspiration from Foundation and offer both failure reasons and recovery suggestions in your error feedback.

Image Be precise. The more your error traces back to a specific fail point, the better able an end programmer will be able to use it to fix the code or respond with runtime workarounds.

Image Incorporate details. Swift errors enable you to create structures, associate values, and provide vital context about where and why things went wrong. Create more informative errors with details.

Image Prefer clarity to concision. Don’t eliminate words just for the sake of short error messages. Yes: “Unable to access uninitialized data store.” No: “Uninitialized.” At the same time, limit your explanations to the issue your error is dealing with. Avoid unneeded extras.

Image Add support. When you incorporate API and documentation references, you further help explain the condition and support recovery. Links are good. Snippets can be good. Full documentation is unnecessary. Allow features like Quick Help to properly fill their role without attempting to usurp them.

Image Avoid jargon. Avoiding jargon is especially important if your error may be consumed outside the context of your immediate working environment. When in doubt, prefer simpler and more common words to project-specific names and acronyms.

Image Be polite. Use phrasing that does not insult your cubical mate, your manager, or the persons who developed the API you’re struggling with. Minimize humor as humor travels poorly. An error message that’s meant to be self-deprecating may misfire at some future point.


Note

Communicating error conditions to end users lies outside the scope of this chapter.


Naming Your Errors

I don’t use a lot of rules for error naming tasks, but I do want to offer a few recommendations in this space:

Image When dealing with trivial applications, feel free to use trivial names, especially in playgrounds, sample code, and test apps:

enum Error: ErrorType {case WrongFile, ItsMonday, IFeelCranky}

Image Use the word Error in type names. Yes: FileProcessingError. No: FileProcessing.

Image Clearly describe the error circumstances in enumeration cases and structure names. Yes: FileNotFound (enumeration case) or FileNotFoundError (struct). No: Missing.

Image Support enumeration cases with labeled associated values:

case FileNotFound(fileName: String)

Adding String Descriptions

ErrorType was first introduced solely for enumerations, and it was later extended to classes and structures. There’s a tendency for Swift developers to create enumerations to represent errors, but unless your code represents true enumerated conditions, there’s no real advantage in, for example, using ParseError.UnexpectedQuoteToken over ParseError("Encountered unexpected quote token"). When there are true enumerated conditions, the former example enables error consumers to know what the exact parsing error is. This allows you to implement specific workarounds on a per-type basis. Using string-typed errors limits the consumer to knowing the error type (a parse error) without establishing the kind of parse error it was.

Prefer enumerations whenever doing so makes sense, as when there’s a distinct syntactic set of possible error conditions. For example, use enumerations when you expect the consumer to use a switch statement to differentiate actions depending on the error that was thrown. Prefer structs for non-enumerated conditions where the focus lies with error information and all errors will be treated equally. In this latter case, where the focus lies more with developer communication than programmatic action selection, it helps to incorporate string descriptions to document why errors arose.

Adding Reasons

Supplying clear informative errors is simplified with strings. They provide the opportunity to pass information about errors without adhering to a predetermined domain of error conditions. The following snippet builds an ErrorType-conforming structure that accepts an arbitrary string description:

struct MyErrorType: ErrorType {
    let reason : String
}

The default initializer establishes the reason property, which travels with the error struct when it is thrown:

throw MyErrorType(reason: "Numeric input was out of range")

This custom error prints as follows:

MyErrorType(reason: "Numeric input was out of range")

Simplifying Output

To remove the structure overhead you see in the preceding example, conform to CustomStringConvertible or CustomDebugStringConvertible, as in the following snippet:

struct MyErrorType: ErrorType, CustomDebugStringConvertible {
    let reason : String
    var debugDescription: String {
        return "(self.dynamicType): (reason)"
    }
}

This updated error prints more simply, presenting only the type and the reason, as in the following example:

MyErrorType: Numeric input was out of range

You can bundle this behavior into a protocol, as in Recipe 6-1, and then conform error types to the new protocol instead of ErrorType.

Recipe 6-1 Self-Describing Error Types


public protocol ExplanatoryErrorType: ErrorType, CustomDebugStringConvertible {
    var reason: String {get}
    var debugDescription: String {get}
}

public extension ExplanatoryErrorType {
    public var debugDescription: String {
        // Adjust for however you want the error to print
        return "(self.dynamicType): (reason)"
    }
}


In the following example, instances of CustomErrorType use the default print behavior provided by the protocol extension:

public struct CustomErrorType: ExplanatoryErrorType {
    public let reason: String
}

Extending String

ErrorType actually enables you to extend String so you can throw string instances as errors. The following example throws, catches, and prints "Numeric input was out of range" but without the MyErrorType overhead:

extension String : ErrorType {}
do {throw "Numeric input was out of range"} catch {print(error)}

While this is a handy solution for simple applications, there are drawbacks. This approach requires that you universally conform String to ErrorType with a single point of conformance. Redundant conformances raise compile-time errors. If you use the trick in one file, and then use the same trick in a module, you’ll run into issues.

Type-Specific Errors

Use type-specific public errors to ensure that ErrorType definitions are local to the types that throw them. Don’t feel that repetitive code like this:

public struct FileInitializationError: ErrorType {let reason: String}

and this detracts from your development work:

public struct ServiceError: ErrorType {let reason: String}

On the contrary, context-specific type names add utility in describing error sources. Their minimal implementations add little overhead to your projects, even though their code looks nearly identical. Adding errors to the type that utilizes them means they travel with the code where they’re relevant. This way, you avoid cross-file definitions and limit redundant definitions across project source files. Nest error types wherever possible into a parent construct for even stronger ties between the types that utilize the errors and the errors themselves.

Retrieving Context

Knowing where an error comes from adds context to understanding and utilizing it. In Recipe 6-2, a simple extension to ErrorType produces a string that describes the source of an error. This extension implements its contextString method by leveraging built-in Swift keywords (__FILE__, __FUNCTION__, __LINE__). These keywords describe the context of a calling scope. Used as default values, they automatically populate parameters for function name, filename, and line number.

Recipe 6-2 Adding Context to Errors


extension ErrorType {
    public func contextString(
        file : String = __FILE__,
        function : String = __FUNCTION__,
        line : Int = __LINE__) -> String {
            return "(function):(file):(line)"
    }
}


Because this is an ErrorType extension, the contextString() method is available to all conforming types. You can use this approach to modify an error before throwing it, normally by assigning the context to a property from the throwing context. If you try to use contextString() from the initializer instead, the context string will report the initializer file, line, and function, which is not what you want. Here’s an example of how you can use this approach:

struct CustomError: ErrorType {
    let reason: String
    var context: String = ""
    init(reason: String) {
        self.reason = reason
    }
}

class MyClass {
    static func throwError() throws {
        var err = CustomError(reason: "Something went wrong")
        err.context = err.contextString()
        throw err
    }
}

You can use this approach to add context to logging as well as to customize your errors.


Note

You can also build context-grabbing features into ErrorType initializers. The write-up at http://ericasadun.com/2015/06/22/swift-dancing-the-error-mambo/ offers an example of how you might use this approach. This post was written before Swift 2 extended error types to structures and classes; you no longer need to manually build _domain and _code properties.


Contextualizing Strings

The following variation on Recipe 6-2 enables you to grab context using a standalone function:

func fetchContextString(file : String = __FILE__,
    function : String = __FUNCTION__,
    line : Int = __LINE__) -> String {
        return "(function):(file):(line) "
}

This approach enables you to grab context wherever you construct strings. For example, you can use the function to construct a reason property for a thrown error:

struct MyError: ErrorType {let reason: String}
do {
    throw MyError(reason: fetchContextString() + "Something went wrong")
} catch { print(error) }

Or, if you’re already working with string errors, you can directly prepend a string with its context:

extension String: ErrorType {}
do {
    throw fetchContextString() + "Numeric input was out of range"
} catch {print(error)}

In this scenario, strings are, themselves, error types. While this is a convenient solution for simple apps, it inherits the problematic issues with using String as an error type.

Contextualizing Throwing Types

Recipe 6-3 offers another slightly more complicated take on contextualization. In this approach, the context task is redirected to the type that implements the failable method—in this example, MyStruct. In this recipe, any type that conforms to Contextualizable can use a default constructContextError method to build and throw a ContextualizedErrorType. The constructor accepts an arbitrary number of arguments, offering more flexibility in error reporting. Unlike Recipe 6-2, Recipe 6-3’s protocol implementation enables you to refer to the throwing context’s dynamic type.

Recipe 6-3 Using a Contextualizable Protocol to Build Errors


// Contextual error protocol
public protocol ContextualizedErrorType : ErrorType {
    var source: String {get set}
    var reason: String {get set}
    init(source: String, reason: String)
}

// Enable classes to contextualize their errors
public protocol Contextualizable {}
public extension Contextualizable {
    // This constructor accepts an arbitrary number of items
    public func constructContextError <T:ContextualizedErrorType>(
        errorType: T.Type,
        _ items: Any...,
        file : String = __FILE__,
        function : String = __FUNCTION__,
        line : Int = __LINE__) -> T {
        return T(
            source:"(function):(self.dynamicType):(file):(line) ",
            reason:items.map({"($0)"}).joinWithSeparator(", "))
    }
}

// This custom error type conforms to ContextualizedErrorType and can
// be constructed and thrown by types that conform to Contextualizable
public struct CustomErrorType: ContextualizedErrorType {
    public var reason: String
    public var source: String
    public init(source: String, reason: String) {
        self.source = source
        self.reason = reason
    }
}

// The conforming type can build and throw a contextualized error
public struct MyStruct: Contextualizable {
    func myFunction() throws {
        throw constructContextError(
            CustomErrorType.self, "Some good reason", 2, 3)
    }
}


As you can see from this recipe, setup involves more steps, but the actual error construction and throwing portion is quite simple. Normally I’d want to prepend the constructContextError method with self for clarity. You cannot do that in this case, though, since it is implemented in a protocol extension.

Simplifying Contexts

Recipe 6-4 offers one last context approach, a simplified version of Recipe 6-3. This recipe retains detail-gathering features but limits thrown errors to a single Error type. Since the source context is included in the error’s source member, this implementation offers a realistic trade-off.

Recipe 6-4 Simpler Context Errors


public struct Error: ErrorType {
    let source: String; let reason: String
    public init(_ source: String = __FILE__, _ reason: String) {
        self.reason = reason; self.source = source
    }
}

protocol Contextualizable {}
extension Contextualizable {
    func contextError(
        items: Any...,
        file : String = __FILE__,
        function : String = __FUNCTION__,
        line : Int = __LINE__) -> Error {
        return Error(
            "(function):(self.dynamicType):(file):(line) ",
            items.map({"($0)"}).joinWithSeparator(", "))
    }
}

public struct Parent: Contextualizable {
    func myFunction() throws {
        throw contextError("Some good reason", 2, 3)
    }
}


Calling Throwing Functions

As Yoda may not have put it, “try or try not, there is no do.” In Swift, there is try. There is also do. Knowing how these features work enables you to hook your methods into the new error-handling system.

Mark any throwing method with throws or rethrows and call these throwing items using try, try?, or try!. The undecorated try operator provides core error-handling behavior. The try? operator (known to its closest friends as try-with-a-question-mark) acts as a bridge between error-handling methods and optional-handling consumers. The exclamation point try! represents the forced-try expression. It enables you to skip the do-catch tango.

A few facts:

Image The throws keyword is automatically part of a function’s type (and the same goes for rethrows). Non-throwing functions are subtypes of throwing functions. So you can use the try operator with non-throwing functions.

Image When currying, the throws applies only to the innermost function.

Image You can’t override non-throwing methods with throwing ones, but you can override a throwing method with a non-throwing method.

Image Throwing functions cannot satisfy protocol requirements for non-throwing functions, but you can satisfy a throwing requirement with a non-throwing implementation.

Using try

Unlike with optionals, whose fail case is limited to nil sentinels, error handling enables you to determine what went wrong and offer runtime mechanisms to respond to those conditions. Swift offers flexible errors that you can build to encapsulate any relevant information needed to report and recover from failure. You participate in this system by calling failable throwing methods with try. Place the try keyword before any call to a throwing method or function:

try myFailableCall()

To respond to thrown errors, wrap the try call in a do-catch construct:

do {
    try myFailableCall()
} catch {
    // handle error here
}

The preceding snippet uses a single catch clause that matches all errors. do-catch is also designed to support multiple clauses using both pattern matching and where clauses, as in the following example:

enum StateError : ErrorType {
    case GeneralError // simple enumeration case
    case UnsupportedState(code: Int, reason: String) // associated values
}

// Throw an error with associated values
func myFailableCall() throws {
    if !testForSuccess() {
        throw StateError.UnsupportedState(code: 418, reason: "I'm a teapot")}
}

do {
    try myFailableCall()
} catch StateError.GeneralError {
    print("General Error")
} catch StateError.UnsupportedState(
    let code, let reason) where code == 418 {
    print("Unsupported Coffee state: reason (reason)")
} catch {
    print("Error is (error) of some kind")
}

The final catch clause is exhaustive in the preceding example. It matches all thrown errors. Although this clause does not use any let or var assignments, by default it has access to a local constant named error, which stores the newly caught error. This error constant is not available to pattern-matching clauses.

Error Propagation

When a do statement is not exhaustive, its error propagates to the surrounding scope, as in the following example:

func partiallyHandle() throws -> String {
    do {
        try myFailableCall()
    } catch StateError.UnsupportedState(let code, _) where code == 0 {
        // this will never happen because code is never 0
        return("Error Condition")
    }
    return "Success Condition"
}

In this contrived example, the catch statement is rigged to never succeed. This function requires the throws keyword as errors are not fully handled within its scope. The preceding code is functionally equivalent to the following:

func partiallyHandle() throws -> String {
    try myFailableCall()
    return "Success Condition"
}

If the catch were, instead, exhaustive, the function could be converted to the following non-throwing version:

func handle()-> String {
    do {
        try myFailableCall()
    } catch {
        return("Error Condition")
    }
    return "Success Condition"
}

In this case, the function loses the throws keyword, and the error-handling chain ends. The function returns a valid String value, even when its try statement fails.


Note

In the real world, partial handling might occur when dealing with the special case of NSUserCancelledError or, when deleting a file, NSFileNoSuchFileError. Both cases are sometimes rerouted into the success path because they represent a positive match to user expectations and desires. For example, consider a function that removes cached files, such as func removeMyCachedFile() throws, which internally handles NSFileNoSuchFileError but reports all other errors to the caller.


Using try!

Forced try (try!) wraps your calls in a runtime assertion made with the highest-quality assertions lovingly crafted through the magic of artisan compilation to disable error propagation:

try! myFailableCall() // may succeed, may crash

When they fail, they fail loudly, throwing runtime errors instead of passing errors to handlers for in-app resolution. Your app crashes, and your users begin writing your gluten-free, allnatural, one-star reviews.

So why would you use this command? try! is meant for calls that throw but that you pretty much know a priori will never fail, such as when they’re documented to never throw for the parameters you’re calling them with. Normally you test for those fail conditions first, and only then do you force your try. As a rule, don’t try! unless you’re sure a throwing function won’t throw. A forced try basically says, “Just go ahead and perform this throwing operation, and if it fails, so be it.” Try! is particularly handy for quick playground hits where you just want something to run, and you don’t really care if it fails and crashes.

Using try?

The try? operator offers a bridge between Swift’s error-handling system and optionals. It returns an optional value that wraps successful results and “catches” errors by returning nil. Use it with standard optional handlers, as in the following examples:

guard let result = try? somethingThatMayThrow() else {
    // handle error condition and leave scope
}
if let result = try? somethingThatMayThrow() {}
let result = (try? somethingThatMayThrow()) ?? myFallbackValue

In each of these examples, the code conditionally binds a non-optional to a result, and the error is discarded. Instead of this pre-Swift 2 approach:

if let value = request(arguments, error:&error) {
   ...success..
} else {
   ...print error and return/die...
}

or this non-optional Swift 2 error-handling implementation:

do {
    let value = try request(arguments)
} catch {... print error and return/die ...}

you work in a system where error conditions are transformed automatically into nil values. This approach offers these benefits:

Image Interoperability between Swift’s nullable optionals and its error system. For example, you can write throwing functions and consume them with if-let. try? bridges these paradigms.

Image A focus on success/failure where the calling context solely assumes responsibility for reporting error conditions. “I tried to do some task and it failed somewhere.” Traditionally errors describe what went wrong. Now you describe what you were trying to do.

What you lose is this:

Image Cocoa-style error handling. Any errors that are generated are eaten away, and you won’t see them or know about them.

Image Error source information such as the file and routine that generated the issue. All you know is that the calling chain failed somewhere, but you don’t know why or how.

Image Railway/freight-train-style development, where you combine functions with a binding operator to propagate errors through the end of the chain.

So, when do you want to use this error-to-optional bridging? Here are a couple of scenarios you might consider this:

Image When you’re focused more on success/failure than on why things failed

Image When you’re working with well-tested API chains, so if you can’t construct a URL or save to a file or whatever, you just want to note it and move on with your recovery code for a “did not succeed” scenario

Implementing Alternatives to try?

The nice thing about try? is that you don’t have to encapsulate calls within a do-catch block. Your result returns as an optional: .Some for success, .None for failure. You can use try? both in if-let statements and with guard. The bad thing about try? is that it discards errors, leaving you unable to figure out what when wrong when things go wrong. That’s never a great thing.

You can roll your own alternative by implementing a simple result enumeration, as in Recipe 6-5. This enumeration represents both success and failure cases in a single type.

Recipe 6-5 Bypassing try? with a Custom Result Enumeration


enum Result<T> {
    case Value(T)
    case Error(ErrorType)

    func unwrap() throws -> T {
        switch self {
        case .Value(let value): return value
        case .Error(let error): throw error
        }
    }

    // Value property
    var value: T? {
        if case .Value(let value) = self { return value }
        return nil
    }

    // Error property
    var error: ErrorType? {
        if case .Error(let error) = self { return error }
        return nil
    }

    init(_ block: () throws -> T) {
        do {
            let value = try block()
            self = Result.Value(value)
        } catch {
            self = Result.Error(error)
        }
    }
}


With Recipe 6-5, instead of using the try operator directly, as in this example:

let result = try myFailableCoinToss()

you call the Result constructor:

let result = Result(myFailableCoinToss)

To unwrap your result outside of if-let and guard, use switches and pattern matching:

switch result {
case .Value(let value): print("Success:", value)
case .Error(let error): print("Failure:", error)
}
if case .Value(let value) = result {
    print("Success:", value)
} else if case .Error(let error) = result {
    print("Failure:", error)
}

Guarding Results

Recipe 6-5’s implementation enables you to use the value and error properties with a standard guard statement. Unlike with try?, the error is ready for use and propagation. This example uses fatalError because it’s demonstrated in a sample playground. In real-world code, you’d throw, wrap, or handle the error. You’d also want to work around the forced-unwrapping used here:

// guard the result value, otherwise handling the error with forced unwrap.
guard let unwrappedResult = result.value else {
    fatalError("(result.error!)")
    // leave scope
}

// result is now usable at top level scope
print("Result is (unwrappedResult)")

Building a Printing Version of try?

Recipe 6-6 offers another approach that more closely mimics try? while printing any errors that are raised. This attempt function performs the same tasks as try? with a custom implementation.

Recipe 6-6 Mimicking try? with Printing


func attempt<T>(block: () throws -> T) -> Optional<T>{
    do {
        return try block()
    } catch {
        print(error)
        return nil
    }
}


This recipe’s approach earns you the if-let and guard behavior of try? but ensures a measure of respect to returned errors. You call it as follows:

let result = attempt(myFailableCoinToss)

In this approach, you can’t base error-recovery strategies on the error type and its details, but it’s not completely throwing away that information either.

Working with guard and defer

When working in Swift’s updated error-handling system, rely on guard and defer to ensure robust execution. These constructs enable you to control whether code is permitted to proceed through the current scope and add mandatory clean-up tasks that run when control leaves that scope. Recipe 6-7 shows the practical interaction of these three elements in a function that executes a system call using popen.

Recipe 6-7 Optimizing guard and defer


public struct ProcessError : ErrorType {let reason: String}

// Execute a system command return the results as a string
public func performSystemCommand(command: String) throws -> String {

    // Open a new process
    guard let fp: UnsafeMutablePointer<FILE> = popen(command, "r") else {
        throw ProcessError(reason: "Unable to open process")
    }; defer{fclose(fp)}

    // Read the process stream
    let buffer: UnsafeMutablePointer<UInt8> =
        UnsafeMutablePointer.alloc(1024); defer {buffer.dealloc(1024)}
    var bytes: [UInt8] = []
    repeat {
        let count: Int = fread(buffer, 1, 1024, fp)
        guard ferror(fp) != 0 else {
            throw ProcessError(reason: "Encountered error while reading stream")
        }
        if count > 0 {
            bytes.appendContentsOf(
                Array(UnsafeBufferPointer(start:buffer, count:count)))
        }
    } while feof(fp) == 0

    guard let string =
        String(bytes: bytes, encoding: NSUTF8StringEncoding) else {
        throw ProcessError(reason:"Process returned unreadable data")
    }
    return string
}


I chose this example because it provides a pleasantly diverse set of defer and guard statements. The recipe showcases these two commands at multiple points.

A guard statement introduces one or more prerequisites. If met, it allows execution to continue in the current scope. If not, the guard’s mandatory else clause executes and must exit the current scope. guard is most commonly used in tandem with conditional binding, as in the string initialization step at the end of Recipe 6-7. If the string cannot be created, the binding fails, and the else clause throws an unreadable-data error. If it succeeds, it makes the string variable available in the outer scope, unlike other cases of optional binding.

guard isn’t limited to working with optionals, as you see in the ferror test that checks the process stream’s error indicator. This is a simple Boolean test that returns true or false. Since execution should not continue on a stream error, a guard statement ensures that a thrown error exits the function scope.

You cascade guard statements with comma-separated clauses as you do with if-let. This creates a simple way to establish variables at the start of your methods and functions, with a single error-handling block to handle any failed condition statements.

defer adds commands whose execution is delayed until the current code block prepares to exit. You can close files, deallocate resources, and perform any other clean-up that might otherwise need to be handled from early exit fail conditions in a single call. I like to add my defer statement right next to the setup calls that require their execution. Recipe 6-7’s defer calls close streams and free memory.

Each defer block executes at the end of a scope whether or not errors were encountered. This ensures that they run whether the function returns a value or throws an error. Swift stores its defer blocks in a stack. They execute in reverse order of their declarations. In this example, the deallocation always precedes fclose.

Wrap-up

Swift’s error system introduces a positive way to respond to and mitigate errors without having to test sentinels and unwrap values before use. Error propagation ensures that you work with errors only when failure conditions are of interest to your code, enabling you to avoid repetitive and error-prone middleman implementations. Swift’s new approach simplifies error handling and returns optionals to their original purpose, for when nils are of actual interest to their callers instead of just stand-ins for “something went wrong.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.255.24