Nil coalescing operator for dictionary
Trying use like yours give you and error :
Left side of mutating operator has immutable type '[Int]?'
By putting parentheses it will be no compile error and it will work
var myDict = [String : [Int]]()
myDict["Algebra"]?.append(contentsOf: [98,78,83,92]) ?? (myDict["Algebra"] = [98,78,83,92])
print(myDict) // ["Algebra": [98, 78, 83, 92]]
Swift Documentation is here for Infix Operators.
What is the role of the nil-coalescing operator ?? in optional chaining?
That's not the nil-coalescing operator you are seeing. The nil-coalescing operator is a binary operator, that needs to be placed between an Optional value and a non-Optional one or two Optional values. In your code example, that's clearly not the case.
What you are seeing is optional chaining on a nested-Optional.
let nestedOptional: Int?? = nil
nestedOptional??.description
You can create Optional values whose wrapped value is also an Optional, in which case you need to unwrap each optional layer one-by-one.
Using nil-coalescing operator with try? for function that throws and returns optional
Essentially, this has to do with the grammar of the try
operator. When used with a binary expression without brackets, try
applies to the whole binary expression, so this:
try? machine.itemCode(code: 0) ?? "Unknown"
is the same as:
try? (machine.itemCode(code: 0) ?? "Unknown")
Since itemCode
throws an error, the latter part of the expression ?? "Unknown
is ignored, and the try?
expression evaluates to nil.
On the other hand, the second expression is like this:
(try? machine.itemCode(code: 0)) ?? "Unknown"
The try?
expression is evaluated first (to nil), then the ??
is applied, evaluating the whole expression to "Unknown".
Nil-Coalescing Operator without changing value
You can design your own infix operator:
infix operator ?=
func ?=<T>(lhs: inout T, rhs: T?) {
lhs = rhs ?? lhs
}
var a = "a"
var b: String? = "b"
a ?= b
print(a) // "b\n"
Nil Coalescing Operator with mixed types
You can write some thing like this:
let statusCodeString = statusCode?.description ?? "None"
Or if you want to work with some types which does not have the property description
:
let statusCodeString = statusCode.map{String(describing: $0)} ?? "None"
Why is the nil coalescing operator ?? returning nil?
To see what is going on here, assign the dictionary look up to a constant:
let name = mysteryInc["Scooby-Doo"]
print(type(of: name))
Output:
Optional<Optional<String>>
So, name
is a double Optional, a String??
.
When the nil coalescing operator is applied, it unwraps the outer Optional and leaves an Optional<String>
(aka String?
). In the example in the question, Swift treats the String literal "no last name"
as type String?
so that ??
can be applied.
If you examine the value of name
you will find that it is Optional(nil)
. All dictionary look ups return Optionals because the key might not exist (in which case they return nil
). In this case, the mysteryInc
dictionary is of type [String: String?]
. The value corresponding to "Scooby-Doo"
is nil
which then gets wrapped into an Optional (because of the dictionary look up) to become Optional(nil)
.
Finally, the value Optional(nil)
is unwrapped by ??
. Since Optional(nil) != nil
, Swift unwraps Optional(nil)
and returns nil
instead of returning the expected "no last name"
.
And that is how you can get nil
from the nil coalescing operator. It did unwrap the Optional; there just happened to be a nil
inside of that Optional.
As @LeoDabus noted in the comments, the correct way to deal with this situation is to conditionally cast the name to String
before applying the nil coalescing operator:
let lastname = mysteryInc["Scooby-Doo"] as? String ?? "no last name"
In reality, it is best to avoid having Optionals as values for your dictionary for the very reason that @Sulthan raises:
What does mysteryInc["Fred"] = nil
do?
Assigning nil
to a dictionary entry removes that entry from the dictionary, so mysteryInc["Fred"] = nil
doesn't replace Optional("Jones")
with nil
, it removes the "Fred"
entry altogether. To leave "Fred"
in the dictionary and make his last name nil
, you need to assign mysteryInc["Fred"] = Optional(nil)
or mysteryInc.updateValue(nil, forKey: "Fred")
.
Swift 4.2, Xcode 10.2 nil coalescing operator warning
As mentioned by OOPer, the solution is to provide a default Any
value as right hand side of the operator, in this case an [Any]
, because the NSOrderedSet
has no specific type bound to it. The solution is:
return [
"sublevels": (self.sublevels?.array ?? []) as NSObject
]
For more info on this matter I suggest you take a look at type casting. At the bottom of the page there is an explanation about casting the Any
type.
why multiple optionals(wrap nil) compare with operator == return false?
There are four kinds of values that a Int???
can have:
.none
(nil
).some(.none)
(a non nilInt???
wrapping a nilInt??
).some(.some(.none))
(a non nilInt???
wrapping a non nilInt??
wrapping a nilInt?
).some(.some(.some(n)))
wheren
is anInt
(anInt
wrapped by 3 layers ofOptional
s)
Similarly, there are three kinds of values that an Int??
can have
.none
.some(.none)
.some(.some(n))
wheren
is anInt
When you write nil
, it always means .none
of whatever type that context needs, so both opt1
and opt2
are .none
here.
What happens when you pass them to the ==
operator? Well, After some overload resolution/type inference, the compiler finds that ==
takes a two Int???
parameters, but you have passed an Int??
as the second argument. Luckily, there exists a conversion from any value t
of type T
to type T?
- .some(t)
.
So after being passed into the ==
, opt2
changes from .none
to .some(.none)
, as you can see from this code snippet:
func test(lhs: Int???, rhs: Int???) {
if case .none = lhs, case .some(.none) = rhs {
print("lhs is .none and rhs is .some(.none)")
}
}
test(lhs: opt1, rhs: opt2) // prints
Hence they are "not equal".
The debugger seems to be showing both .none
and .some(.none)
as "nil", possibly because both of their debugDescription
/description
is "nil".
If you don't care about the multiple layers of optionals, you can just unwrap them to a single layer by doing as? Int
, then compare:
print((opt1 as? Int) == (opt2 as? Int)) // true
Related Topics
Using Nstimer in Swift Playground
How to Restrict the Type That a Function Throws in Swift
How to Handle Multiple Network Call in Alamofire
Uitesting Xcode 7: How to Tell If Xcuielement Is Visible
Is It the Right Way Using '[Weak Self]' in Swift Closure
Swift: Loop Over Array Elements and Access Previous and Next Elements
Scenekit Some Textures Have a Red Hue
In Swift, Why Does Assigning to a Static Variable Also Invoke Its Getter
How to Set Countdowntimer Mode in Datepicker on Swiftui
How Does Typecasting/Polymorphism Work with This Nested, Closure Type in Swift
Mkpointannotations Touch Event in Swift
Updating Uitableview with Multiple Sections from Rlmresults.Observe()
How to Delete from Firebase Database
Class-Only Generic Constraints in Swift