Why do we need to explicitly cast the optional to Any?
Every type can be implicitly promoted to an optional of that type. This means that when you cast T?
to Any
it is very hard to know whether it was originally T
or originally T?
(or even T??
or worse). Most confusing is that Any
can be promoted to Any?
and that Any?
is of type Any
, so telling the difference between Any
, Any?
, Any??
, and Any???
(etc.) is very tricky, and sometimes impossible.
Any
is a very tricky type in Swift and should almost never be used. Except for explicitly tricking the compiler (in some very fancy and fragile type-eraser), I don't know of any case where it really makes sense to have Any
as a variable type, and definitely not in the form of [Any]
. If you're created an [Any]
, you've gone down a bad path that isn't going to go well.
There are a very few cases where Any
as a function parameter type makes sense (print()
being the most famous), but they are extremely rare in app-level code. If you find yourself needing Any
, you've probably done something wrong, and the compiler is going to fuss at you about it and often make you write extra as
code to make sure you really mean the messy things you're saying.
Just to give some concrete versions of this, optionality tends to be lost when you enter Any
. So consider this situation:
let number: Int = 3
let optionalNumber: Int? = 3
let nilNumber: Int? = nil
let anyNumber = number as Any
let anyOptional = optionalNumber as Any
let anyNil = nilNumber as Any
if anyNumber is Int { print("number is Int")} // yes
if anyOptional is Int { print("optional number is Int")} // yes
if anyNil is Int { print("nil is Int")} // no
if anyNil is Int? { print("nil is Int?")}
// -> Error: Cannot downcast from 'Any' to a more optional type 'Int?'
Rats.
We can't get our optional back the same way we put it in. We can promote it of course:
if (anyNil as Any?) is Int? { print("nil is Int?") } // yes
But we can promote anything that way, since everything is implicitly an optional of itself:
if (anyNumber as Any?) is Int? { print("number is Int?")} // also yes
So, um. Rats. We don't really know if it was originally optional or not. It's mess, and the compiler is warning you that it's going to be a mess if you go very far down this road. T->Any is a bit of magic. T->T? is also a bit of magic. Combine the two magics, and you had better know exactly what you're doing.
Why non optional Any can hold nil?
TL;DR; Optionals in swift are translated by the compiler to Optional
enum instances, and since Any
can map to any value, it can be used to store optionals.
How does Swift represent optionals? It does it by mapping SomeType?
to a concrete implementation of the Optional
enum:
Int? => Optional<Int>
String? => Optional<String>
A simplified declaration of Optional
looks like this:
enum Optional<T> {
case none // nil
case some(T) // non-nil
}
Now, a variable of type Any
is able to hold an enum value (or any other kind of value, or even metatype information), so it should be able to hold for example a nil
String, aka String?.none
, aka Optional<String>.none
.
Let's see what happens, though. As we see by the Optional
declaration, nil
corresponds to the .none
enum case for all types:
nil == Optional<String>.none // true
nil == Optional<Int>.none // true
[Double]?.none == nil // also true
So theoretically, you should be able to assign nil
to a variable declared as Any
. Still, the compiler doesn't allow this.
But why doesn't the compiler let you assign nil
to an Any
variable? It's because it can't infer to which type to map the .none
enum case. Optional
is a generic enum, thus it needs something to fill the T
generic parameter, and plain nil
is too broad. Which .none
value should it use? The one from Int
, the one from String
, another one?
This gives an error message supporting the above paragraph:
let nilAny: Any = nil // error: nil cannot initialize specified type 'Any' (aka 'protocol<>')
The following code works, and is equivalent to assigning a nil
:
let nilAny: Any = Optional<Int>.none
, as the above Any
variable is actually holding a valid value of the Optional
enum.
Indirect assignments work too, as behind the scenes nil
is converted to Optional<Type>.none
.
var nilableBool: Bool? // nilableBool has the Optional<Bool>.none value
var nilBoolAsAny: Any = nilableBool // the compiler has all the needed type information from nilableBool
Unlike other languages, in Swift nil
corresponds to a concrete value. But it needs a type to work with, for the compiler to know which Optional<T>.none
it should allocate. We can think of the keyword as providing sugar syntax.
Why if I typecast String? to Any, Xcode gives off warning, but not with AnyObject?
According to Swift Language Guide you are expected to get a warning when casting optional to Any (see note at the bottom of the page). You can get rid of warning by casting optional value to Any
as shown below.
let email : String?;
let password : String?;
let dict = ["email": email as Any, "password": password as Any] as [String: Any];
Why should Java 8's Optional not be used in arguments
Oh, those coding styles are to be taken with a bit of salt.
- (+) Passing an Optional result to another method, without any semantic analysis; leaving that to the method, is quite alright.
- (-) Using Optional parameters causing conditional logic inside the methods is literally contra-productive.
- (-) Needing to pack an argument in an Optional, is suboptimal for the compiler, and does an unnecessary wrapping.
- (-) In comparison to nullable parameters Optional is more costly.
- (-) The risk of someone passing the Optional as null in actual parameters.
In general: Optional unifies two states, which have to be unraveled. Hence better suited for result than input, for the complexity of the data flow.
coerced to Any' but property is of type UIColor
This has nothing to do with .foregroundColor
. It has everything to do with .tintColor
and setTitleTextAttributes
.
This parameter is of type [NSAttributedString.Key : Any]
. It is not in any way considering the documentation for each key. It doesn't know or care that this should be a UIColor
. If you passed "squid", this would compile without warning (it wouldn't work, but it would compile):
UIBarButtonItem.appearance().setTitleTextAttributes(
[
.font: UIFont.systemFont(ofSize: 40),
.foregroundColor: "squid",
], for: .normal)
All it's looking at is that you're assigning view.tintColor
to a value of type Any
.
The problem is that view.tintColor
is not UIColor
, it's UIColor!
. It's not actually possible for .tintColor
to be nil, but it's possible to set it to nil:
view.tintColor // r 0.0 g 0.478 b 1.0 a 1.0
view.tintColor = .red
view.tintColor // r 1.0 g 0.0 b 0.0 a 1.0
view.tintColor = nil
view.tintColor // r 0.0 g 0.478 b 1.0 a 1.0
That makes sense in ObjC, but the only way to express it in Swift is to use a !
type. When you assign !
types to other things, they become ?
types. And that means that you're using UIColor?
in a place that accepts Any
(the value of the dictionary).
Using an optional as Any
can be dangerous because it creates a lot of weird corner cases. For example, you can't round-trip an optional through Any; it gets squashed into its base type:
let x: Any = Optional(1)
x as? Int? // Cannot downcast from 'Any' to a more optional type 'Int?'
x as? Int // 1
There are a lot of these kinds of little sharp-edges when working with Any.
Of course you didn't mean to work with Any. It's not your fault. But this is why Swift is complaining.
There are several solutions, depending on what you like. You can use a !
:
.foregroundColor: view.tintColor!
You can add as Any
to silence the warning:
.foregroundColor: view.tintColor as Any
Personally I'd use as Any
.
Or you can be elaborate and unload the value earlier (I don't recommend this):
let tintColor = view.tintColor ?? .blue
UIBarButtonItem.appearance().setTitleTextAttributes(
[
.font: UIFont.systemFont(ofSize: 40),
.foregroundColor: tintColor,
], for: .normal)
Typescript variables with ? and?
When you write this:
class Optional {
id?: number;
}
you have declared that id
is an optional property. That means it may or may not be present in an instance of Optional
. If it is present, the value should be a number
(or possibly undefined
unless you are using the --exactOptionalPropertyTypes
compiler option). If it is not present, then if you read the id
property, the value will be undefined
.
The above resolves the compiler warning because you are not required to initialize a property that's optional; it will just be undefined
when you read it.
Anyway that means you cannot just use the id
property as a number
:
const o = new Optional();
o.id.toFixed(2) // compiler error!
//~~ <-- Object is possibly undefined
That warning is good, right? Because you don't want to accidentally read the toFixed()
method of undefined
. You need to check for undefined
first, possibly by using the optional chaining operator (?.
):
o.id?.toFixed(2) // okay
Optional properties are a reasonable approach when you are not sure if the properties will be assigned by the time users want to access them, and you want to protect these users from potential runtime errors.
On the other hand, when you write this:
class Asserted {
id!: number
}
you have asserted that the id
property has definitely been assigned. That assertion is you telling the compiler something it can't verify for itself. This is useful in situations where you actually do assign the property but the compiler cannot follow the logic:
class ActuallyAssigned {
id!: number
constructor() {
Object.assign(this, { id: 123 });
}
}
An instance of ActuallyAssigned
will have the id
property set in the constructor, because the Object.assign()
function copies properties into the first parameter from all subsequent parameters. But the compiler cannot understand this, and so without the definite assignment assertion, you'd get a warning. Silencing that warning is reasonable here.
But in Asserted
above, it's not reasonable. You have lied to the compiler. It can't see that id
has be assigned because it actually has not been assigned. And so you will not get a compiler warning if you go ahead and treat the id
property like a number
, even though it might be undefined
:
a.id.toFixed(2) // no compiler error, but
// RUNTIME ERROR: o.id is undefined
Definite assignment assertions are therefore not recommended for situations when properties will quite possibly be undefined
when users want to access them.
Playground link to code
Related Topics
Swiftui Call Function on Variable Change
Getting String Name of Objective-C @Objc Enum Value in Swift
Higher Order Function: "Cannot Invoke 'Map' with an Argument List of Type '((_) -> _)'"
Why am I Allowed Method Access Less Restrictive Than Class Access
Storing/Passing Function Types from Swift Protocols
Could Not Find an Overload for '/' That Accepts the Supplied Arguments
Is Swift a Dynamic or Static Language
Can't Create a Range in Swift 3
How to Line Break Long Large Title in iOS 11
Swiftui Background Color of List MAC Os
Difference Between Associated and Raw Values in Swift Enumerations
Swiftui List Empty State View/Modifier
Unexpectedly Large Realm File Size
Swift 4.0 Mapview Running Slow
How to Retrieve a Random Object from Firebase Using a Sequential Id
Swiftui: How to Present View When Clicking on a Button
Is There an Alternative to Initialize() in MACos Now That Swift Has Deprecated It