Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Swift4 cast an array of UIButton! to [UIButton?] type?

Tags:

ios

swift

swift4

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?

like image 659
Kamil Harasimowicz Avatar asked Jan 03 '18 16:01

Kamil Harasimowicz


1 Answers

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 nils, if there are any, and will return an array of unwrapped type:

let array = [button1, button2].flatMap { $0 }
like image 72
Milan Nosáľ Avatar answered Sep 21 '22 19:09

Milan Nosáľ