Почему Swift4 использует массив UIButton! к типу [UIButton?]?

13

Сегодня я встретил странную проблему. Посмотрите на этот код:

class A {

    var button1: UIButton!
    var button2: UIButton!

    func foo() {
        let array = [button1, button2]
    }
}

Xcode говорит, что array является [UIButton?] . По какой-то причине Swift4 бросает UIButton! элементов в UIButton? . Почему?

    
задан Kamil Harasimowicz 03.01.2018 в 17:31
источник

3 ответа

13

ОБЪЯСНЕНИЕ

ImplicitlyUnwrappedOptional не является отдельным типом, а нормальный Optional с атрибутом, объявляющим его значение, может быть неявно принудительным (на основе SE-0054 ):

  

Однако появление! в конце типа объявления свойства или переменной больше не указывает, что декларация имеет тип IUO; скорее, это указывает, что (1) объявление имеет необязательный тип, и (2) объявление имеет атрибут, указывающий, что его значение может быть принудительно принудительно. (Ни один человек никогда не напишет или не увидит этот атрибут, но мы будем называть его как @_autounwrapped.) Такое объявление в дальнейшем упоминается как декларация IUO.

Таким образом, когда вы используете это:

let array = [button1, button2]

Компилятор выводит тип array на [UIButton?] , потому что тип button1 и button2 % %__%%, а не Optional<UIButton> (даже если только один из них был необязательным, он будет выводиться необязательный тип).

Подробнее в SE-0054 .

Боковое примечание:

Это поведение действительно не связано с ImplicitlyUnwrappedOptional<UIButton> , в следующем примере тип arrays будет выведен на button2 , хотя есть UIButton? , и есть значение, установленное в ! :

var button: UIButton! = UIButton()

func foo() {
    let button2 = button // button2 will be of optional type: UIButton?
}

Решение

Если вы хотите получить массив развернутого типа, у вас есть два варианта:

Сначала , поскольку Гай Когус предложил в своем ответе использовать явно выраженный тип позволить быстро получить его:

let array: [UIButton] = [button1, button2]

Однако, если за один шанс одна из кнопок содержит button , это приведет к сбою nil .

В то время как используя неявно развернутый необязательный вместо необязательного ( Unexpectedly found nil вместо ! ), вы утверждаете, что в этих кнопках никогда не будет нуля, я бы предпочел более безопасный вариант второй предложенный EmilioPelaez в своем комментарии. То есть использовать ? , который будет фильтровать flatMap s, если они есть, и вернет массив развернутого типа:

let array = [button1, button2].flatMap { %pre% }
    
ответ дан Milan Nosáľ 03.01.2018 в 17:44
источник
7

Поскольку UIButton! не является типом, или, скорее, UIButton? с некоторыми соглашениями. Значение ! всегда неявно разворачивает необязательный. Следующий

 var x: UIButton!
 // Later
 x.label = "foo"

Является синтаксическим сахаром для

 var x: UIButton?
 // Later
 x!.label = "foo"

Когда вы создаете массив из них. У компилятора есть выбор неявно разворачивать их и вызывать [UIButton] или оставлять их как необязательные и вызывать [UIButton?] . Это делается для более безопасного использования двух вариантов.     

ответ дан JeremyP 03.01.2018 в 17:44
источник
1

Swift играет его безопасно, предполагая, что они являются опциями, а не разворачивают их по умолчанию, так как они могут технически быть nil . Если вы попытаетесь явно пометить их как неявно-распакованные, как этот

let array: [UIButton!] = [button1, button2]

вы получите следующую ошибку:

  

ошибка: неявно развернутые опции могут быть разрешены только на верхнем уровне и в виде результатов функции

В этом случае, если вы хотите, чтобы они были развернуты, просто определите его как

let array: [UIButton] = [button1, button2]
    
ответ дан Guy Kogus 03.01.2018 в 17:37
источник