In Swift, how do I avoid both optionals and nil object references?

Ivan X

The whole reason for optionals is to prevent runtime crashes resulting from hitting a variable assigned to nil/null/none. Therefore, variables can't be nil; instead they can be wrapped in an Optional type that represents them as Some or None, and unwrapped to get the specific contents of Some, or nil.

But if you unwrap them all over the place with !, or with Implicitly Unwrapped Optionals, you just introduce the possibility, since you're an imperfect coder, of runtime crashes. If you use if let to safely unwrap them, you avoid crashes, but you are stuck inside the scope of the if let statement to work with the value inside Some, and you still have to handle the potential nil case. If you use ? to unwrap temporarily for calling a method, it will be rewrapped when complete, introducing the messy possibility of multiply layered optional wrapping.

So: It appears to me that the thing to do is avoid optionals except when necessary (e.g. when calling framework methods that return them). But if I'm not using optionals, that means my object references need to be non-nil, and I can't figure out how to handle cases where, for one reason or another, there shouldn't be, or isn't, a value assigned to an object reference.

My question is: How do I avoid the need for nil? It seems like it requires a different programming approach. (Or am I supposed to just use optionals, and if that's what I'm doing, how is it any better than simply having null assignments to object references like other languages have?)

I know this is potentially a subjective question, but where else am I supposed to ask it? I'm not trying to troll or stir up debate, I'd really like to know what the correct approach is as I write more Swift code.

Airspeed Velocity

You’re right, optionals can be a pain, which is why you shouldn't overuse them. But they're more than just something you have to deal with when using frameworks. They're a solution to an incredibly common problem: how to handle a call that returns a result that might be OK, or might not.

For example, take the Array.first member. This is a handy utility that gives you the first element of an array. Why is it useful to be able to call a.first, when you could just call a[0]? Because at runtime the array might be empty, in which case a[0] will explode. Of course you can check a.count beforehand – but then again

a. you might forget,

and

b. that results in quite ugly code.

Array.first deals with this by returning an optional. So you are forced to unwrap the optional before you can use the value that is the first element of the array.

Now, to your issue about the unwrapped value only existing inside the block with if let. Imagine the parallel code, checking the array count. It would be the same, right?

if a.count > 0 {
    // use a[0]
}
// outside the block, no guarantee
// a[0] is valid

if let firstElement = a.first {
    // use firstElement
}
// outside the block, you _can't_
// use firstElement

Sure, you could do something like perform an early return from the function if the count is zero. This works but is a bit error-prone – what if you forgot to do it, or put it in a conditional statement that didn't happen to run? Essentially you can do the same with array.first: check the count early in the function, and then later on do array.first!. But that ! is like a signal to you – beware, you are doing something dangerous and you will be sorry if your code isn't completely correct.

Optionals also help make the alternatives slightly prettier. Suppose you wanted to default the value if the array was empty. Instead of this:

array.count > 0 ? a[0] : somedefault

you can write this:

array.first ?? somedefault

This is nicer in several ways. It puts the important thing up front: the value you want is how the expression starts, followed by the default. Unlike the ternary expression, which hits you first with the checking expression, followed by the value you actually want, then finally the default. It's also more foolproof – much easier to avoid making a typo, and impossible for that typo to result in a runtime explosion.

Take another example: the find function. This checks if a value is in a collection and returns the index of its position. But the value may not be present in the collection. Other languages might handle this by returning the end index (which doesn't point to a value but rather one past the last value). This is what's termed a "sentinel" value – a value that looks like a regular result, but actually has special meaning. Like the previous example, you’d have to check the result was not equal to the end index before using it. But you have to know to do this. You have to look up the docs for find and confirm that's how it works.

With find returning an optional it's just natural, when you understand the optional idiom, to realize that the reason it does is because the result might not be valid for the obvious reason. And all the same things about safety mentioned above also apply – you can’t accidentally forget, and use the result as an index, because you have to unwrap it first.

That said, you can over-use optionals as they are a burden to have to check. This is why array subscripts don't return optionals – it would create so much hassle to have to constantly check and unwrap them, especially when you know for a fact that the index you're using is valid (for example, you're in a for loop over a valid index range of the array) that people would be using ! constantly, and thus clutter their code without benefit. But then the helper methods like first and last are added to cover common cases where people do want to quickly do an operation without having to check the array size first, but want to do it safely.

(Swift Dictionaries, on the other hand, are expected to be regularly accessed via invalid subscripts, which is why their [key] method does return an optional)

Even better is if the possibility of failure can be avoided altogether. For example, when filter matches no elements, it doesn’t return a nil optional. It returns an empty array. “Well obviously it would”, you might say. But you’d be surprised how often you see someone making a return value like an array optional when actually they just ought to return an empty one. So you’re completely correct in saying you should avoid optionals except when they’re necessary – it’s just a question of what necessary means. In the examples above I’d say they’re necessary, and a better solution to the alternatives.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How do I safely unpack optionals (AlamoFire response object) in Swift

Swift optionals nil check

Swift: Testing optionals for nil

How can I create an inline dictionary in Swift while omitting potentially nil optionals as values?

Do Optionals default to nil?

Swift: why do I need optionals anyways?

How do you avoid UIGraphicsGetCurrentContext() returning "nil" in Swift?

How do I avoid undefined method `empty?' for nil:NilClass?

How do you unwrap Swift optionals?

How can I achieve overloading both generic and non-generic function signatures for optionals in Swift 5.1 (Xcode 11 Playground)?

Assigning nil to generic optionals in Swift

How do I remove all circular references from an object in JavaScript?

How to Nil a object in Swift

How do I declare an array of weak references in Swift?

How do I reference the same schema for both GET and POST requests in OpenAPI 3 if the schema has other references

How do I avoid broken cross-references when moving figure captions?

How do I avoid a warning when I have a function that can take both mutable and immutable char*

How do i retrieve both the key and the value from a json object?

How do I avoid duplicate object entry in Mongodb?

Swift Combine, Avoid nested subscribers on optionals

How to avoid object references for spawned objects in Unity 2D?

How to check object is nil or not in swift?

In Swift, how do I set every element inside an array to nil?

How do I check if an object is a collection? (Swift)

How Do I append CoreData Object with Swift?

How do I return safely unwrapped optionals which are outside of their scope?

How can i avoid circular references in type aliases

How do object references work internally in javascript

How to set optionals that are nil to an empty string?

TOP Ranking

HotTag

Archive