Swift3 optionals chaining in IF conditions bug?
Optional comparison operators are removed from Swift 3.
SE-0121
You need to write something like this:
if test?.optionalInt ?? 0 > 4
{
}
Why can't Swift's greater-than or less-than operators compare optionals when the equality operators can?
It makes perfect sense for the equality operator to support optionals, because it's absolutely clear that for any integer valued variable i
:
nil == nil
nil != i
i != nil
i == i
if and only if their values are the same
On the other hand, it's not clear how comparison to nil
should act:
Is i
less than nil
?
- If I want to sort an array so that all the
nil
s come out at the end, then I would wanti
to be less thannil
. - But if I want to sort an array so that all the
nil
s come out at the start, then I would wanti
to be greater thannil
.
Since either of these are equally valid, it wouldn't make sense for the standard library to favor one over the other. It's left to the programmer to implement whichever comparison makes sense for their use-case.
Here's a toy implementation that generates a comparison operator to suite either case:
func nilComparator<T: Comparable>(nilIsLess: Bool) -> (T?, T?) -> Bool {
return {
switch ($0, $1) {
case (nil, nil): return false
case (nil, _?): return nilIsLess
case (_?, nil): return !nilIsLess
case let (a?, b?): return a < b
}
}
}
let input = (0...10).enumerated().map {
$0.offset.isMultiple(of: 2) ? Optional($0.element) : nil
}
func concisePrint<T>(_ optionals: [T?]) -> String {
return "[" + optionals.map { $0.map{ "\($0)?" } ?? "nil" }.joined(separator: ", ") + "]"
}
print("Input:", concisePrint(input))
print("nil is less:", concisePrint(input.sorted(by: nilComparator(nilIsLess: true))))
print("nil is more:", concisePrint(input.sorted(by: nilComparator(nilIsLess: false))))
Output:
Input:
[0?, nil, 2?, nil, 4?, nil, 6?, nil, 8?, nil, 10?]
nil is less:
[nil, nil, nil, nil, nil, 0?, 2?, 4?, 6?, 8?, 10?]
nil is more:
[0?, 2?, 4?, 6?, 8?, 10?, nil, nil, nil, nil, nil]
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.
Using the Swift if let with logical AND operator &&
As of Swift 1.2, this is now possible. The Swift 1.2 and Xcode 6.3 beta release notes state:
More powerful optional unwrapping with if let — The if let construct
can now unwrap multiple optionals at once, as well as include
intervening boolean conditions. This lets you express conditional
control flow without unnecessary nesting.
With the statement above, the syntax would then be:
if let tabBarController = window!.rootViewController as? UITabBarController where tabBarController.viewControllers.count > 0 {
println("do stuff")
}
This uses the where
clause.
Another example, this time casting AnyObject
to Int
, unwrapping the optional, and checking that the unwrapped optional meets the condition:
if let w = width as? Int where w < 500
{
println("success!")
}
For those now using Swift 3, "where" has been replaced by a comma. The equivalent would therefore be:
if let w = width as? Int, w < 500
{
println("success!")
}
Optional Chaining in one step?
Strictly spoken Example 2 is neither optional binding nor optional chaining because String(format...)
returns a non-optional String
and the format
parameter must be non-optional, too.
Example 1 is the correct and recommended syntax to handle the optionals.
Edit: I totally agree with Alexander's answer (except that String(format:)
returns String?
)
Binary operator ' =' cannot be applied to operands of type 'String.IndexDistance?' (aka 'Optional Int ') and 'Int'
The reason is that Equatable
works with optionals and Comparable
does not. You have to unwrap the optional.
A suitable and safe solution is to optional bind the text
property:
if let password = textFieldPassword.text, password.count >= 8 { ... }
Related Topics
How to Convert Hex Number to Bin in Swift
What Are the Rules for Spaces in Swift
Multiple Type Constraints in Swift
Dispatchsourcetimer and Swift 3.0
Use Queue and Semaphore for Concurrency and Property Wrapper
Can't Programmatically Change Color Set in Storyboard as Color from Xcassets Catalog
Change Color Searchbar Result Icon Swift
Swiftui: Global Overlay That Can Be Triggered from Any View
Is Force Cast Really Bad and Should Always Avoid It
How to Move to Next Textfield in Swiftui
Reading Data into a Struct in Swift
@Noescape Attribute in Swift 1.2
Programmatically Navigate to New View in Swiftui
Swift: 'Hashable.Hashvalue' Is Deprecated as a Protocol Requirement;
How to Use a @Fetchrequest with the New Searchable Modifier in Swiftui