Swift Optional Chaining Doesn't Work in Closure

Swift optional chaining doesn't work in closure

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.

Optional chaining in Swift Closure where return type has to be Void

Optional chaining wraps whatever the result of the right side is inside an optional. So if run() returned T, then x?.run() returns T?. Since run() returns Void (a.k.a. ()), that means the whole optional chaining expression has type Void? (or ()?).

When a closure has only one line, the contents of that line is implicitly returned. So if you only have that one line, it is as if you wrote return weakSelf.rscript?.run(). So you are returning type Void?, but dispatch_async needs a function that returns Void. So they don't match.

One solution is to add another line that explicitly returns nothing:

dispatch_after(time, dispatch_get_main_queue()) {
weakSelf.rscript?.run()
return
}

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

The problem in a nutshell ? the function for fractions reports a fault whereas the function for decimal numbers fails to detect bad input.

The function for decimal numbers does detect “bad” input. However, "700" does not contain ".", and you only call processDecimal(s:) if the string does contain ".". If the string doesn't contain "." and also doesn't contain "/", doubleFromDecimalOrFraction(s:) doesn't call any function to parse the string.

Optional chaining not working for optional protocol requirements

It turns out that the website version of the iBook content for The Swift Programming Language (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html) shows the ThreeSource declared with @objc. Either doing this or deriving ThreeSource from NSObject works.

enter image description here

Is Swift optional chaining lazily evaluated left-to-right?

Optional chains are lazily evaluated left-to-right as you'd expect. The first optional in the chain to fail stops any further evaluation of the chain.

// Playground test for left-to-right lazy evaluation.
struct C {
static var i = 0
func chain(count: Int) -> C? {
print("Expected \(count) -> \(C.i)")
assert(count == C.i)
C.i += 1
return C.i > 2 ? nil : self
}
}

let c = C()
c.chain(0)?.chain(1)?.chain(2)?.chain(3)!.chain(4)!

Output

Expected 0 -> 0
Expected 1 -> 1
Expected 2 -> 2

how to extend optional chaining for all types

is there no way to make this generic function work as a protocol method?

No, you must "explicitly apply the protocol to the types I care about".

However, you are in fact reinventing the wheel. This is the use case of flatMap/map. If both foo and bar are optional, you can write:

bar.flatMap { foo?.doSomething($0) }

Note the lack of ? after bar. You are calling flatMap on Optional, rather than bar's type. If doSomething returns T, the above expression will return T?.

If only bar is optional, use map:

bar.map { foo.doSomething($0) }

Cannot convert the expression's type' with optional in closure body

An expression containing a single expression is inferred to return that result, hence:

let closure: (number: Int) -> Void = { (number) -> Void in
optional?.updateValue(number, forKey: "key")
}

is equivalent to:

let closure: (number: Int) -> Void = { (number) -> Void in
return optional?.updateValue(number, forKey: "key")
}

You now have conflicting return types between Void and Int? (remember, updateValue returns the old value)

Splitting it up with an explicit return clarifies the inferred typing.

let closure: (number: Int) -> Void = { (number) -> Void in
optional?.updateValue(number, forKey: "key")
return
}

Are `self?` and `guard let self` the same, in a closure?

These are practically identical. The main difference is that the first will check whether self is nil twice rather than once. Since the first example doesn't hold a strong reference across statements, it is technically possible for the first line to execute, then self to be released, and the second line not execute. In the second case, the guard let takes a strong reference until the end of the block, at which point self will be released.

The latter is generally preferable, though it's not a huge issue either way. It is easier to reason about the guard let code when self is nil. When you skip over a bunch of statements using self?., any lines that don't rely on self still execute, which might be surprising. It is also nice in cases where optional chaining isn't appropriate (for example when accessing properties of self). So it works the same way in more cases.

Combining optional chaining and ifs

You can use the nil coalescing operator here, and give x a value greater than 5, if its nil:

if (x ?? 6) > 5 {
// do your thing
}

In regards to the question update, you can use map on the optional to achieve your goal:

if x.map({ $0.x > 5 && $0.y > 6 }) ?? true {
// do your thing
}

map has the advantage that it avoids the forced unwrap.



Related Topics



Leave a reply



Submit