Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: call methods of an optional variable

So I know the difference between '!' and '?', I just want to know what is the best way to use them when calling a method of an optional variable

var bar: Bar? = nil

bar?.doSomething()  // this will be valid, but wouldn't call doSomething
bar!.doSomething() // Given error: EXC_BAD_INSTRUCTIONS (Obviously)

But when 'bar' is not nil, these two method-calls are both valid.

bar = Bar()
bar?.doSomething()  // Valid
bar!.doSomething()  // Valid

So my question is, what is the best way to call methods of an optional variable, I personally use:

if bar != nil {
    bar!.doSomething()
}

or will bar?.doSomething() do exactly the same?

like image 455
Youri Nooijen Avatar asked Mar 13 '15 08:03

Youri Nooijen


People also ask

How do you call a function optional in Swift?

You specify optional chaining by placing a question mark ( ? ) after the optional value on which you wish to call a property, method or subscript if the optional is non- nil . This is very similar to placing an exclamation point ( ! ) after an optional value to force the unwrapping of its value.

How does Swift handle optional value?

Optional Handling. In order to use value of an optional, it needs to be unwrapped. Better way to use optional value is by conditional unwrapping rather than force unwrapping using ! operator.

How do you declare an optional variable in Swift?

If you defined a variable as optional, then to get the value from this variable, you will have to unwrap it. This just means putting an exclamation mark at the end of the variable. Optional("Hello, Swift 4!")


2 Answers

I think it is bad advice to avoid bar?.doSomething() and always use if let bar = bar { ... } instead.

There are two main reasons:

First, bar?.doSomething() simply is more concise. If you are writing an if let with just one statement in the conditional expression then you should probably ask yourself if you don't want to put that code on a single line with the shorter version.

Although this is perfectly legal code ..

if let thing = thing {
    thing.bar()
}

.. the following is just as legal, just a lot shorter ..

thing?.bar()

Second, more important and to the core of this issue, they are not the same! The if let is a combined test, optional unwrapping and assignment while bar? does something called Optional Chaining.

The latter is super useful in a number of cases that lets you write much nicer code. For example:

It can be used to replace this ..

var imageGenerator: ImageGenerator?

if let generator = imageGenerator {
    myView.image = generator.generateImage()
} else {
    myView.image = nil
}

.. simply with:

myView.image = self.imageGenerator?.generateImage()

The reason for this is because the Optional Chaining returns nil as soon as it encounters nil in the chain. And since UIImageView has an image: UIImage?, it takes that nil value. Even if it comes from a ImageGenerator?

It is even more useful when combined with the Nil Coalescing Operator, ??.

Now you can turn this code ..

var imageGenerator: ImageGenerator?

if let generator = imageGenerator {
    myView.image = generator.generateImage()
} else {
    myView.image = DefaultImage
}

.. into ..

myView.image = generator?.makeImageGenerator() ?? DefaultImage

.. which is way more concise.

And because Optional Chaining works on Chains .. you can even do something like:

myView.image = delegate?.makeImageGenerator()?.generateImage() ?? DefaultImage

Which would become ..

if let delegate = delegate {
    if let generator = delegate.generatorForImage() {
        if let image = generator.generateImage() {
            myView.image = image
        } else {
            myView.image = DefaultImage
        }
    } else {
        myView.image = DefaultImage
    }
} else {
    myView.image = DefaultImage
}

.. if you did not use the Optional Chaining and the Nil Coalescing Operator.

(Yes the myView.image = DefaultImage could be done only conditionally at the end, but that means you still have to introduce additional code to check if it was set. It wont be much shorter.)

There, two new tricks :-)

like image 85
Stefan Arentz Avatar answered Sep 22 '22 21:09

Stefan Arentz


When you do

bar?.doSomething() 

it is called Optional Chaining: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html

That means, in case bar is nil, it won't continue to doSomething method, but return a nil. If you want to handle cases where it is nil, you should check if an object contains nil or not. You could also do it like this, by optional binding:

if let unwrappedBar = bar {
    unwrappedBar.doSomething()
} else {
    doSomeOtherThing() // In case it's nil
}

It makes the value available inside the if let statement as a temporary constant called unwrappedBar (you can give it any other name if you want), if it isn't nil.

You can also use a shorter syntax with optional chaining and coalescing operator:

bar?.doSomething() ?? doSomeOtherThing()

Here's more info about different ways to access optional values: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID330

like image 27
Aleksi Sjöberg Avatar answered Sep 26 '22 21:09

Aleksi Sjöberg