dynamicType of optional chaining not the same as assignment
TLDR;
The playground sidebar/column will dynamically resolve expressions in the playground, be it values assigned to variables (mutables/immutables) or just "free-floating" non-assigned values.
Your first example applies dynamicType
to a value, which will resolve to the type of that specific value (true.dynamicType
: Bool.Type
).
Your second example, on the other hand, applies dynamicType
to a variable (an immutable, but I'll use variable here to differ from value), which must have a concrete type, and hence will resolve to a type that can hold any kind of wrapped values (true
or false
) as well as nil
(here, nil
is, specifically, Optional<Bool.Type>.None
), no matter what value the variable actually holds. Hence, the dynamicType
will resolve to Optional<Bool.Type>.Type
in your second example.
Details
The value displayed in the playground sidebar/column generally follows the following display rules:
For an assignment expression, the value shown in the sidebar is the value assigned, e.g.
var a = 4 // shows '4'
a = 2 // shows '2'
let b: () = (a = 3)
/* shows '()': the _value_ assigned to 'b', which is the _result_
of the assignment 'a = 3', to which a _side effect_ is that 'a'
is assigned the value '3'. */For an expression that contains no assignment, the value shown in the sidebar is generally the result of the expression, e.g.
true // shows 'true'
1 > 3 // shows 'false'
let c = 3
c // shows '3'
c.dynamicType // shows 'Int.Type'
In your first example (lines 2-3), we have no assignment, and the playground will dynamically resolve the value(/result) of the expression prior to resolving the dynamicType
of that value. Since we're dealing with optionals, the value is either simply the value of the wrapped type (in this case, true
), or the value is a type specific .None
. Even if the playground shows e.g. the result of let a: Int? = nil
as just nil
in the sidebar, the value shown is in fact not the same .None
(nil
) as for say let b: String = nil
- For
let a: Int? = nil
, the value ofa
is in factOptional<Int.Type>.None
, - whereas for
let b: String? = nil
, the value ofb
isOptional<String.Type>.None
With this in mind, it's natural that the resolved dynamicType
of a non-nil
value will be the concrete wrapped type (in your example, Bool.Type
is naturally the type of true
), whereas the resolved dynamicType
of a nil
value will include both the general optional and the wrapped type information.
struct Foo {
let bar: Bool = true
}
var foo: Foo? = Foo()
/* .Some<T> case (non-nil) */
foo?.bar // true <-- _expression_ resolves to (results in) the _value_ 'true'
foo?.bar.dynamicType // Bool.Type <-- dynamic type of the _result of expression_
true.dynamicType // Bool.Type <-- compare with this
/* .None case (nil) */
foo = nil
foo?.bar.dynamicType // nil <-- _expression_ resolves to the _value_ 'Optional<Foo.Type>.None'
Optional<Foo.Type>.None.dynamicType
// Optional<Foo.Type>.Type <-- compare with this
Now, if you assign the values to a variable, naturally the variable must have a concrete type. Since the value we assign at runtime can be either .None
or .Some<T>
, the type of the variable must be one that can hold values of both these cases, hence, Optional<T.Type>
(disregarding of whether the variable holds nil
or a non-nil
value). This is the case which you've shown in your second example: the dynamicType
of the variable (here, immutable, but using variable to differ from value) isOk
is the type that can hold both .None
and .Some<T>
, no matter what the actual value of the variable is, and hence dynamicType
resolves to this type; Optional<Bool.Type>.Type
.
Wrapping expressions in parantheses escapes the runtime introspection of the Swift Playground?
Interestingly, if an expression is wrapped in parantheses prior to applying .dynamicType
, then the playground sidebar resolves .dynamicType
of the wrapped expression as the type of the expression, as if its actual value was unknown. E.g., (...)
in (...).dynamicType
is treated as a variable with a concrete type rather than a runtime-resolved value.
/* .Some case (non-nil) */
foo?.bar // true
(foo?.bar).dynamicType /* Optional<Bool>.Type <-- as if (...)
is a _variable_ of unknown value */
/* .None case (nil) */
foo = nil
(foo?.bar).dynamicType /* Optional<Bool>.Type <-- as if (...)
is a _variable_ of unknown value */
We can further note that any lone expression wrapped in parantheses in the playground will not resolve to anything at all (in the sidebar). It's as if we escape the sidebar:s runtime introspection if wrapping expressions in parantheses (which would explain why the dynamicType
of expressions wrapped in parantheses will resolve as if the playground cannot make use runtime information of these expressions)
var a = 4 // shows '4'
(a = 2) // shows nothing; can't expand or get details in sidebar
Tbh, I cannot explain why this is, and will categorize it as a peculiarity of the Swift playground.
Why can't I call Optional methods on an optional-chained type?
This is an operator precedence issue. You need to add parentheses to change the order:
let bar = (foo?.count).map { $0 * 2 }
Consider the case of a Collection like String rather than an Int:
let foo: String? = "ABC"
let bar = foo?.uppercased().map(\.isLowercase) // [false, false, false]
In this case, it's sensible that the .map
applies to the String rather than to the Optional. If the precedence were the other way, then you'd need a ?.
at every step (or a lot of parentheses :D)
Is Swift optional chaining always done with the if let construction, or is it just done using a question mark with an optional?
The conclusion is correct - let
is an independent, but useful, construct. In context it introduces a binding only within the if-body and executes the if-body only if the bound value is not-nil. (Technically it unwraps an optional binding.)
let
does not affect how the expression on the right (with or without chaining) is handled. For instance, if someMasterObject
were optional/nil it would fail and not "chain" - even with let
.
When one or the other (or both) is more "correct" depends on the situation: eg. what is being chained and what the corrective action should be.
For instance, if someMasterObject
could be nil, we might have the following which uses both chaining and let
. Also note how the return value matters and is not simply discarded or "nil on failure":
if let handler = someMasterObject?.possiblyNilHandler{
return handler.handleTheSituation()
} else {
return FAILED_TO_CALL
}
Then compare it with a non-equivalent chained form, which would only return nil
in the failed-to-call case, but nil might be a valid return value from handleTheSituation
!
return someMasterObject?.possiblyNilHandler?.handleTheSituation()
On the other hand, do consider that there is always direct translation of chaining to nested if-let statements:
result_of_expression = someMasterObject?.possiblyNilHandle?.handleTheSituation()
if let master = someMasterObject {
if let handler = master.possiblyNilHandler {
result_of_expression = handler.handleTheSituation()
} else {
result_of_expression = nil
}
} else {
result_of_expression = nil
}
How does the '?' unwrap an optional in a case let declaration?
This is syntactic sugar for option patterns. The docs on option pattern says:
An optional pattern matches values wrapped in a
some(Wrapped)
case of anOptional<Wrapped>
enumeration. Optional patterns consist of an identifier pattern followed immediately by a question mark and appear in the same places as enumeration case patterns.
Thus, your code is the same as:
var x: Int? = 42
if case .some(let a) = x {
print(a)
}
It's not typical for simple if
statements as you can just do this instead:
if let a = x {
print(a)
}
But consider an enum wrapped in an optional:
enum Foo {
case bar
case baz
}
let y: Foo? = .bar
switch y {
case .none: break
case .some(.bar): break
case .some(.baz): break
}
This switch can be written in a more succinct way using some sugar:
switch y {
case nil: break
case .bar?: break
case .baz?: break
}
How to use guard statement to detect nil after an assignment?
The other answers show you how to solve your issue, but doesn't really explain why this error occurs, so I thought I'd pitch in on that.
The guard let ... else
statement, much like if let ...
, attempts to bind the unwrapped value of an optional---generally as long as this is not nil
---to a non-optional immutable of the same underlying type; using optional binding
var a: Int? = 5
if let b = a {
// b unwrapped, type inferred to non-optional type Int
print(b) // "5"
}
The above binding would fail if a
had the value nil
, as b
, as per default (by type inference), is of type Int
which cannot hold nil
.
In this context, it makes no sense to explicitly declare b
to be an implicitly unwrapped optional, as this will allow a successful binding even if a
is nil. An equivalently non-sense block would be to explicitly declare b
to be an optional, whereafter the "attempted optional binding" of optional a
(Int?
) to optional b
(Int?
) would naturally always succeed, and the if let ...
block reduces to a completely redundant block-local assignment.
a = nil
if let b: Int! = a {
print(b) // "nil"
// wups, we managed to bind a to b even though a is nil ...
// note also this peculiarity
print(b.dynamicType) // Optional<Int>
let c: Int! = nil
print(c.dynamicType) // ImplicitlyUnwrappedOptional<Int>
}
if let b: Int? = a {
print(b) // nil
// wups, we managed to bind a to b even though a is nil ...
}
Note the peculiarity that no matter if we explicitly specify b
to be of type Int?
(optional) or type Int!
(implicitly unwrapped optional), the binded immutable b
passing into the if let
block is, for both cases, just a regular optional (of type Int?
). This explains why you need to unwrap event
(event!.iconImgData
) after the guard let
clause, even though we declared it to be of an implicitly unwrapped type.
Hence, in your example, your guard let ... else
statement will not catch cases where eventsImagesLoading.removeValueForKey(location)
is nil
, as the binding to event
(which is of implicitly unwrapped optional type Event!
) will success even for nil
cases.
func foo() {
let bar : Int? = nil
guard let _: Int! = bar else {
print("this is surely nil")
return
}
print("but an implicitly unwrapped optional will fall through")
}
foo() // "but an implicitly unwrapped optional will fall through"
You should generally only use implicitly unwrapped optionals for immutables where you need delayed initialization (value nil
until initialized). After initialization of an implicitly unwrapped optional, its value should never be nil
(whereas in the example above, it's value is, after initialization by optional binding, just that; nil
).
Also, you should generally let the compiler infer the non-optional type of the immutable to which you are trying to bind in the guard let INFER_THIS = ... else
or if let INFER_THIS = ...
clauses.
We could ponder over whether it should even be allowed to use optional binding to optional types (guaranteed success), but that is another discussion.
Related Topics
Swift: Mkannotation Long Title Text
Sound for Scene Transition, That Doesn't Stutter
User Specific Avatar in Jsqmessagesviewcontroller with Sdwebimage
Show Status Bar Only for iPhone X
How to Make UItabbar Image Rounded in Swift Programmatically
Swift: Forward Keystrokes to a Different Process
Why Is Swift Giving Me Inaccurate Floating Point Arithmetic Results
Error: Variable with Getter/Setter Cannot Have an Initial Value
Convert Ble Current Time to Date
How to Access Results from a Realm in Swift
Weird Toolbar with Nested Conditionals Behavior
Generating Random Doable Math Problems Swift
Swift: Guard Let and Where - The Priority
How to Make First Responder in Swiftui Macos
Swift 4: How to Asynchronously Use Urlsessiondatatask But Have The Requests Be in a Timed Queue
Understanding Optional Global Variables in Swift
Passing Values Between Viewcontrollers Based on List Selection in Swift
Skease Action, How to Use Float Changing Action Setter Block