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
ifcurrentBottle
do exists… ornil
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.
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
How to Execute Some Code After a Segue Is Done
Invalid Device State - Xcode/iOS Simulator Error
How to Share Nsdata or Phasset Video Using Facebook iOS Sdk 4.0 Fbsdksharedialog
How to Set an Nscalendarunitminute Repeatinterval on iOS 10 Usernotifications
How to Read Plist Without Using Nsdictionary in Swift
Swift: Failed to Assign Value to a Property of Protocol
Alamofire: Finished with Error - Code: -1001
Paging Uicollectionview by Cells, Not Screen
Completion Block for Popviewcontroller
Switching Between Text Fields on Pressing Return Key in Swift
Couldn't Register [...] with the Bootstrap Server
Calling Performseguewithidentifier Doesn't Call Shouldperformseguewithidentifier
Tracking Mkmapview Centercoordinate While Panning
How to Specify a Platform Target When Running Swift Test from the Cli
Uilabel with Two Different Color Text
iOS Name of This Way of Building and Returning an Object in Objective-C
How to Clear or Clean Specific Pod from the Local Cocoapods Cache