Swift optional chaining doesn't work in closure

matt

My code looks like this. My class has an optional var

var currentBottle : BottleLayer?

BottleLayer has a method jiggle().

This code, using optional chaining, compiles fine in my class:

self.currentBottle?.jiggle()

Now I want to construct a closure that uses that same code:

let clos = {() -> () in self.currentBottle?.jiggle()}

But I get a compile error:

Could not find member 'jiggle'

As a workaround I can force unwrapping

let clos = {() -> () in self.currentBottle!.jiggle()}

or of course I can use full-fledged optional binding, but I'd rather not. I do recognize that optional chaining is just syntactical sugar, but it is hard to see why this syntactical sugar would stop working just because it's in a handler (though there may, of course, be a reason - but it's a surprise in any case).

Perhaps someone else has banged into this and has thoughts about it? Thanks.

AliSoftware

This is NOT a bug. It's simply your closure type which is wrong. The correct type should return an optional Void to reflect the optional chaining:

let clos = { ()->()? in currentBottle?.jiggle() }

The problem in details:

  • You declare your closure as a closure that returns Void (namely ->()).
  • But, do remember that, as like every time you use optional chaining, the return type of the whole expression is of optional type. Because your closure can either return Void if currentBottle do exists… or nil if it doesn't!

So the correct syntax is to make your closure return a Void? (or ()?) instead of a simple Void

class BottleLayer {
    func jiggle() { println("Jiggle Jiggle") }
}
var currentBottle: BottleLayer?
currentBottle?.jiggle() // OK
let clos = { Void->Void? in currentBottle?.jiggle() } // Also OK
let clos = { () -> ()? in currentBottle?.jiggle() } // Still OK (Void and () are synonyms)

Note: if you had let Swift infer the correct type for you instead of explicitly forcing it, it would have fixed the issue for you:

// Even better: type automatically inferred as ()->()? — also known as Void->Void?
let clos = { currentBottle?.jiggle() }

[EDIT]

Additional trick: directly assign the optional chaining to a variable

You can even assign the function directly to a variable, like so:

let clos2 = currentBottle?.jiggle // no parenthesis, we don't want to call the function, just refer to it

Note that the type of clos2 (which is not explicitly specified here and is thus inferred automatically by Swift) in this case is not Void->Void? — namely a function that returns either nil or Void) as in the previous case — but is (Void->Void)?, which is the type for "an optional function of type Void->Void".

This means that clos2 itself is "either nil or is a function returning Void". To use it, you could once again use optional chaining, simply like that:

clos2?()

This will evaluate to nil and do nothing if clos2 is itself nil (likely because currentBottle is itself nil)… and execute the closure — thus the currentBottle!.jiggle() code — and return Void if clos2 is non-nil (likely because currentBottle itself is non-nil).

The return type of clos2?() itself is indeed Void?, as it returns either nil or Void.

Doing the distinction between Void and Void? may seem pointless (after all, the jiggle function does not return anything in either case), but it let you do powerful stuff like testing the Void? in an if statement to check if the call actually did happen (and returned Void namely nothing) or didn't happen (and return nil):

if clos2?() { println("The jiggle function got called after all!") }

[EDIT2] As you (@matt) pointed out yourself, this other alternative has one other major difference: it evaluates currentBottle?.jiggle at the time that expression got affected to clos2. So if currentBottle is nil at that time, clos2 will be nil… even if currentBottle got a non-nil value later.

Conversely, clos is affected to the closure itself, and the optional chaining is only evaluated each time clos is called, so it will evaluate to nil if currentBottle is nil… but will be evaluated to non-nil and will call jiggle() if we call clos() at a later time at which point currentBottle became non-nil.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Why doesn't this closure work?

From Dev

Swift 3.0 Optional Chaining

From Dev

swift optional chaining with cast

From Dev

Optional chaining with Swift strings

From Dev

Optional chaining and Array in swift

From Dev

optional chaining in Swift 3: why does one example work and not the other?

From Dev

Django chaining filters doesn't work

From Dev

Laravel redirect() doesn't work on chaining method

From Dev

.then chaining doesn't work. Why?

From Dev

optional closure property in Swift

From Dev

Javascript: Caching within Closure doesn't work

From Dev

Optional Chaining or ternary expression in Swift?

From Dev

NSClassFromString Doesn't work for Optional Values

From Dev

Chaining $.ajax() for synchronous requests with Q doesn't work

From Java

Swift optional escaping closure parameter

From Dev

Chaining animations in Swift with Spring won't work properly

From Dev

Swift - AVAudioPlayer Doesn't Work

From Dev

swift valueForKeyPath doesn't work

From Dev

NSForegroundColorAttributeName doesn't work in Swift?

From Dev

swift build doesn't work

From Dev

swift valueForKeyPath doesn't work

From Dev

Swift setToolbarHidden doesn't work

From Dev

Closure compiler command line doesn't seem to work with latest version

From Dev

Why doesn't this javascript closure work as I hoped?

From Dev

Dplyr indirection / pipe doesn't work inside a closure

From Dev

Why don't shorthand argument names work in this Swift closure?

From Dev

Is there something like the swift optional chaining in javascript?

From Dev

Optional chaining used in left side of assignment in Swift

From Dev

Accessing boolValue in a NSNumber var with optional chaining (in Swift)

Related Related

HotTag

Archive