Подтвердить что ты не робот

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

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

class A {

    var button1: UIButton!
    var button2: UIButton!

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

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

4b9b3361

Ответ 1

ОБЪЯСНЕНИЕ

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

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

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

let array = [button1, button2]

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

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

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

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

var button: UIButton! = UIButton()

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

Решение

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

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

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

Однако, если на случай, если одна из кнопок содержит nil, это приведет к сбою Unexpectedly found nil.

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

let array = [button1, button2].flatMap { $0 }

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

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