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.
When value is guaranteed there is no point to not unwrap the value. In Cookielab we use force unwrapping in two cases.
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/")!
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
}
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)
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.
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.