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?
I'm finding Swift blog as really the best source for such changes so:
Before Swift 4:
Swift 4
All the quotes taken from Swift blog
Because
UIButton!
is not a type, or rather it is aUIButton?
with some conventions. The!
means always implicitly unwrap the optional. The followingIs syntactic sugar for
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.EXPLANATION
ImplicitlyUnwrappedOptional
is not a distinct type, rather a normalOptional
with an attribute declaring its value may be implicitly forced (based on SE-0054):Thus when you use this:
The compiler derives the
array
type to[UIButton?]
, because the type of thebutton1
andbutton2
isOptional<UIButton>
, notImplicitlyUnwrappedOptional<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 ofbutton2
will be derived toUIButton?
even though there is the!
and there is a value set inbutton
: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:
However, if per chance one of the buttons contains
nil
, it will causeUnexpectedly 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 useflatMap
(compactMap
in Swift 4+) which will filter outnil
s, if there are any, and will return an array of unwrapped type: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 thisyou will receive the following error:
In that case, if you want them to be unwrapped then just define it as