Почему Swift4 создает массив UIButton! [UIButton?] введите?

14

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

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
источник

4 ответа

15

ОБЪЯСНЕНИЕ

ImplicitlyUnwrappedOptional не является отдельным типом, скорее нормальный Optional с атрибутом, декларирующим его значение, может быть принудительно принудительным (на основе 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.

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

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 an optional: UIButton?
}

Решение

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

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

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

Тем не менее, если одна из кнопок содержит nil , это вызовет сбой Unexpectedly found nil .

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

let array = [button1, button2].flatMap { $0 }
    
ответ дан Milan Nosáľ 03.01.2018 в 17:44
  • Идеальное объяснение. Спасибо. –  Kamil Harasimowicz 03.01.2018 в 17:52
  • @KamilHarasimowicz Рад помочь –  Milan Nosáľ 03.01.2018 в 17:53
  • Что-то, что я хотел бы добавить к этому ответу, состоит в том, что если у вас есть массив опций, вы можете использовать array.flatMap {$ 0}, и он станет массивом непараллелей, где значения nil были лишены массива. –  EmilioPelaez 04.01.2018 в 00:53
  • @EmilioPelaez благодарит за предложение об улучшении, я разработал его в ответе –  Milan Nosáľ 04.01.2018 в 07:46
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
  • Я не автор сообщения, но последнее предложение дайте мне понять :) Спасибо –  Retterdesdialogs 03.01.2018 в 17:46
  • Я думаю, что во втором фрагменте должен быть var x: UIButton? Я прав? –  Kamil Harasimowicz 03.01.2018 в 17:49
  • @KamilHarasimowicz да, спасибо - исправлено. –  JeremyP 03.01.2018 в 17:56
1

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

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

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

error: implicitly unwrapped optionals are only allowed at top level and as function results

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

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

Я считаю блог Swift действительно лучшим источником для таких изменений, так что

До 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.

Все цитаты взяты из блога Swift

    
ответ дан Julian Król 12.09.2018 в 10:46