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.
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() }
Void
(namely ->()
).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]
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.
let closure = { () -> () in
self.currentBottle?.jiggle()
return
}
Otherwise the compiler thinks the result of that statement should be returned from the closure and it realizes there is a mismatch between ()
(return type) and the optional returned by the statement (Optional<Void>
). By adding an explicit return
, the compiler will know that we don't want to return anything.
The error message is wrong, of course.
Okay, another approach. This is because closures in Swift have Implicit Returns from Single-Expression Closures. Because of the optional chain, your closure has a return type of Void?
, so:
let clos = {() -> Void? in self.currentBottle?.jiggle()}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With