Result

This is an advanced topic, but it is so so helpful to networking that we will do our best to make it accessible to everyone. That being said, you should have an understanding of enums with associated values. An understanding of generics would be helpful but not required.

Enums with Associated Values

Overview

We can think of Result as being implemented as follows:

enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

Associating Types

It is an enum with two potential cases–a failure response and a success response. Each case has an associated piece of information. We can make the Success type anything that we want. In contrast, our Failure type must adopt the Error protocol.

Every single call to the network manager should return a Result because networking is fraught with potential errors that we must handle. Hence, we need to categorize every error we can with the level of specificity that you choose. To create a type that adopts the Error protocol, we don't need to add any functionality; we just need to add conformance directly in the type declaration or in an extension:

struct MyError: Error {}
class MyError: Error {}
enum MyError: Error {}
// or
extension MyError: Error {}

Personally speaking, I like being specific, so I will actually enumerate many of the potential errors in an enum. But, you can choose to handle errors however you want. I know, on Eatery, we just have a struct that stores a description of the error as a string.

As I mentioned earlier, Success can be anything that we want. For the rest of this article I will just call it MySuccess.

Now that we have a Success type (MySuccess) and a Failure type (MyError) we need to put those into the Result type because that's what our network manager will be passing around.

Associating types to a generic is very easy. We just match the syntax of the Result definition above and substitute in our types: Result<MySuccess, MyError>.

Handling Results

Now that we have our two cases, we need to figure out how to deal with them.

  1. .success(MySuccess)

  2. .failure(MyError)

For a more in-depth discussion, read enums with associated values.

I, personally, like using switch statements because the syntax is clean. When we are processing our errors, we need to check two things: first, we need to check which case we are processing, and second, we need to process the extra associated value.

Switches

A normal switch case will handle the cases, but will not give us access to the associated values. To circumvent this, we need to bind the associated value to a variable. We can do this in two ways with our switch statement:

switch ... {
    case let .success(successInformation): // option 1
        // successInformation: MySuccess = ...
        ...
    case .failure(let failureInformation): // option 2
        ...
        // failureInformation: MyFailure = ...
}

You may decide that you don't care about the associated value. In this case, we don't need to bind anything, but we do still need to handle it. In this case, we use a case not a case-let statement (like a normal switch). But, this will check for equality between the case and switching result. This means that unless we can match the associated value, it will not trip the case. We can fix this by associating every possible value (or actually none)! We do this with the wildcard:

case .failure(_):
    ...

If Statements

You may decide that switches have too much boiler plate, that switches are too scary, or that you do not need to handle all of the cases.

Granted, you do need to understand the general logic between case handling because, to use if statements, we just take a case and put an if before it.

if case let .success(successInformation) = aResult {
    // successInformation: MyResult = ...
    ...
}

if case .success(_) = aResult {
    ...
}

Last updated