I met a strange issue today. Please look at this code:
class A {
var button1: UIButton!
var button2: UIButton!
func foo() {
let array = [button1, button2]
}
}
Xcode says that array
is [UIButton?]
type. For some reason Swift4 casts UIButton!
elements to UIButton?
. Why?
EXPLANATION
ImplicitlyUnwrappedOptional
is not a distinct type, rather a normal Optional
with an attribute declaring its value may be implicitly forced (based on SE-0054):
However, the appearance of ! at the end of a property or variable declaration's type no longer indicates that the declaration has IUO type; rather, it indicates that (1) the declaration has optional type, and (2) the declaration has an attribute indicating that its value may be implicitly forced. (No human would ever write or observe this attribute, but we will refer to it as @_autounwrapped.) Such a declaration is referred to henceforth as an IUO declaration.
Thus when you use this:
let array = [button1, button2]
The compiler derives the array
type to [UIButton?]
, because the type of the button1
and button2
is Optional<UIButton>
, not ImplicitlyUnwrappedOptional<UIButton>
(even if only one of the buttons was optional, it would derive the optional type).
Read more in SE-0054.
Side note:
This behavior is not really related to arrays
, in the following example the type of button2
will be derived to UIButton?
even though there is the !
and there is a value set in button
:
var button: UIButton! = UIButton()
func foo() {
let button2 = button // button2 will be an optional: UIButton?
}
SOLUTION
If you want to get an array of unwrapped type, you have two options:
First, as Guy Kogus suggested in his answer, use explicit type instead of letting swift derive it:
let array: [UIButton] = [button1, button2]
However, if per chance one of the buttons contains nil
, it will cause Unexpectedly found nil
crash.
While by using implicitly unwrapped optional instead of optional (!
instead of ?
) you are claiming that there never will be nil in those buttons, I would still prefer the second safer option suggested by EmilioPelaez in his comment. That is to use flatMap
(compactMap
in Swift 4+) which will filter out nil
s, if there are any, and will return an array of unwrapped type:
let array = [button1, button2].flatMap { $0 }
Because UIButton!
is not a type, or rather it is a UIButton?
with some conventions. The !
means always implicitly unwrap the optional. The following
var x: UIButton!
// Later
x.label = "foo"
Is syntactic sugar for
var x: UIButton?
// Later
x!.label = "foo"
When you create an array of them. The compiler has the choice of implicitly unwrapping them and inferring [UIButton]
or leaving them as optional and inferring [UIButton?]
. It goes for the safer of the two options.
Swift is playing it safe by assuming them to be optionals, rather than unwrapping them by default, since they can technically be nil
. If you try to explicitly mark them as implicitly-unwrapped like this
let array: [UIButton!] = [button1, button2]
you will receive the following error:
error: implicitly unwrapped optionals are only allowed at top level and as function results
In that case, if you want them to be unwrapped then just define it as
let array: [UIButton] = [button1, button2]
I'm finding Swift blog as really the best source for such changes so:
Before Swift 4:
A mental model many people have for implicitly unwrapped optionals is that they are a type, distinct from regular optionals. In Swift 3, that was exactly how they worked: declarations like var a: Int? would result in a having type Optional, and declarations like var b: String! would result in b having type ImplicitlyUnwrappedOptional.
Swift 4
The new mental model for IUOs is one where you consider ! to be a
synonym for ? with the addition that it adds a flag on the declaration
letting the compiler know that the declared value can be implicitly
unwrapped.
In other words, you can read String! as “this value has the type
Optional and also carries information saying that it can be
implicitly unwrapped if needed”.
This mental model matches the new implementation. Everywhere you have
T!, the compiler now treats it as having type T? , and adds a flag in
its internal representation of the declaration to let the type checker
know it can implicitly unwrap the value where necessary.
All the quotes taken from Swift blog