iOS Cookie Crumbs: May the force (unwrap) not be with you

Cookie Crumbs is a series of short blog posts.
Topics may vary, ranging from code examples and development processes to personal opinions.
It's not the whole cookie—just a crumb.

Force unwrapping of optionals is a two-sided sword. One side lets you cut trees and defend yourself, while the other... well, you'll hurt yourself. There are two perspectives (two sides of the sword) on force unwrapping of optionals.

Value guaranteed

When value is guaranteed there is no point to not unwrap the value. In Cookielab we use force unwrapping in two cases.

Constants

When developer is responsible of creating such object/constant it is okay-ish to create force unwrapped constants.

let shareImage = UIImage(systemName: "square.and.arrow.up")!
let googleUrl = URL(string: "https://www.google.com/")!

Late initialisation

Sometimes it isn’t possible to initialize complex objects with circular dependencies all at once. In such cases, force unwrapping can be handy. The compiler is satisfied, and we don’t need to unwrap the property value each time it is accessed.

class Foo {
    var bar: Bar
    
    init(bar: Bar) {
        self.bar = bar
    }
}

class Bar {
    unowned var foo: Foo!
}

func makeFoo() -> Foo {
    let bar = Bar()
    let foo = Foo(bar: bar)
    bar.foo = foo
    return foo
}

Interface design optional

Optional - A type that represents either a wrapped value or the absence of a value. Most optionals are intentional design choices. Choosing to use an optional value is a deliberate decision.

Let’s consider this Profile struct:

struct Profile {
    let name: String
    var nickName: String?
    var avatar: UIImage?
}

Force unwrapping <inline-code> avatar<inline-code> would be bad design. But why? It is the design choice/interface telling us that <inline-code> avatar<inline-code> may or may not be present and we should handle it either way.

let ferrariModelsIOwned: [String] = []
let first = namesOfSuperModelsIDated.first!

Since I’ve never owned any Ferrari my program would crash.

When I come across interface designed optional I am handling it accordingly:

Image(uiImage: profile.avatar ?? .placeholder)

Cut the rope

Part of your program may have dependencies that are optional by design. Often, a subsystem accepts optionals and unwraps them each time the value is needed. A better strategy is to design the subsystem to accept only unwrapped values. Unwrapping optionals once at a higher level in the call hierarchy streamlines the entire subsystem and potentially reduces cyclomatic complexity.

In example below each function for enhancing picture would need to deal with optional.

class PictureEnhancer {
    private var picture: UIImage?
    
    init(picture: UIImage?) {
        self.picture = picture
    }
    
    func addRainbowBackground() throws -> UIImage {
        guard let picture else {
            throw ProfilePicture.error
        }
        // adding rainbow background
        return pictureWithRainbowBackground
    }
    
    func removeBackground() throws -> UIImage {
        guard let picture else {
            throw ProfilePicture.error
        }
        // removing background
        return pictureWithoutBackground
    }
}

To make it more clear PictureEnhancer should accept picture and enhance it without dealing with optionals.

class PictureEnhancer {
    private var picture: UIImage
    init(picture) {
        self.picture = picture
    }
    
    func addRainbowBackground() {
        // adding rainbow background
        return pictureWithRainbowBackground
    }
    
    func removeBackground() -> UIImage {
        // removing background
        return pictureWithoutBackground
    }
}

Automated and manual tests will also be easier.

Conclusion

There is no clear line between right and wrong, but can crash sooner or later vs. can’t crash at all. Be deliberate when dealing with optionals.

iOS Cookie Crumbs: May the force (unwrap) not be with you
Martin Blišťan
iOS Sorcerer
By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.