Есть ли технический недостаток в использовании неявно развернутых опций, но при тестировании на ноль против опционального связывания?

Итак, я знаю, что обычный способ использования опций в Swift - через необязательные привязки, чтобы развернуть их, вот так...

let stringA:String? = nil // (or "ABC")

if let unwrappedStringA = stringA
{
    let x:String = unwrappedStringA
}

Но я также видел следующий способ использования неявно развернутых опций, который, на мой взгляд, выглядит чище, поскольку он не только не требует дополнительной переменной, но и "читает" лучше и больше похож на английский (т.е. "Если это не ноль, тогда...') и удобочитаемость - один из основных принципов Swift (особенно в предстоящем Swift 3.0).

let stringA:String! = nil // (or "ABC")

if stringA != nil
{
    let x:String = stringA
}

Однако, что касается последнего, "пуристы" Свифта называют это "запахом кода" и настаивают, что это "Плохо, плохо, плохо!"... но они никогда не объясняют почему! Итак... почему это так плохо?

Примечание. Да, я знаю об опциональной цепочке и других подобных функциях, которые нельзя использовать с неявно развернутыми опциями, и все они действительно классные функции, но я специально спрашиваю о технических недостатках тестирования неявно развернутых опций против nil против необязательного связывания.

Я надеюсь на количественные, технические причины, по которым одно лучше другого (например, оптимизация компилятора, лучшая проверка безопасности, производительность и т. Д.) Другими словами, это не просто "Эй, потому что это не способ Swifty". сделать это!' Надеюсь, что это имеет смысл.

Обновить

Я на самом деле только что нашел способ решить одну из моих проблем (по крайней мере, поверхностно), которая заключается в создании новой переменной для хранения развернутой переменной. Хитрость здесь в том, что поскольку развернутая переменная фактически находится в другой области видимости, чем сама необязательная, вы можете использовать точно такое же имя.

Кроме того, внутри самих скобок есть еще одна область видимости, которая также может иметь переменную с именем stringA. Это означает, что теперь вы можете иметь три переменные 'stringA' (но это не значит, что вы должны!)...

  1. Необязательный
  2. Развернутый необязательный
  3. Новая локальная переменная внутри скобок

Вот этот сумасшедший код, показывающий это...

let stringA:String? = nil // (or "ABC") // First stringA variable

if let stringA = stringA
{
    let x:String = stringA // <- This stringA is the unwrapped optional (the second stringA)

    // Now we're just getting crazy (and smelly!)
    let stringA = stringA + stringA // <- This is yet another stringA (third one!)
    let y:String = stringA 
}

Опять же, я не потворствую этому!! Я просто показываю некоторый код, который другие могут найти интересным с поучительной точки зрения. Я уверен, что сделал!

3 ответа

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

С дополнительными привязками:

let stringA:String? = nil // (or "ABC")

if let unwrappedStringA = stringA
{
    let x:String = unwrappedStringA
}

accidentallyAttemptingToUse(stringA) // Compiler error! I see my mistake.

С неявной развёрткой:

let stringA:String! = nil // (or "ABC")

if(stringA != nil)
{
    let x:String = stringA
}

accidentallyAttemptingToUse(stringA) // Runtime crash I might find in testing. Yuck.

Для чего нужны IUO?

Тогда зачем вообще неявно распаковывать опционы? Они существуют в основном для одной цели: свойства, которые определенно будут иметь значение, но только после initпоэтому они должны быть необязательными. Часто @IBOutletОни попадают в эту категорию: вы можете доверять им, чтобы они имели значение, и вы не хотите, чтобы их развернули все время, но они не назначены в init, поэтому вы должны сделать их необязательными. Это то, для чего неявно развернутые опционы.

Развертывание в переменную с тем же именем

Ваше обновление, которое предварительно предлагает развернуть дополнительные параметры в переменные с тем же именем, на самом деле является отличным стилем Swift и использовалось Apple рано и часто. Код легче читать, когда вы разворачиваете переменные с одинаковыми именами, потому что вы можете отслеживать меньше (и лучше) имен. Обязательно примите это!

let stringA:String? = nil // (or "ABC")

if let stringA = stringA
{
    let x:String = stringA // Of *course* this is still named `stringA`;
    // it's representing the same concept, after all.
}

Из-за изменения в неявно развернутых опционах в Swift 3 вы будете намного счастливее, если правильно развернете обычные опционы вместо использования неявно развернутых опций.

В Swift 3, если вы назначаете String! для новой переменной, эта переменная будет иметь тип String?, Это означает, что ваш неявно развернутый необязательный параметр становится обычным необязательным при назначении, и теперь вам приходится иметь дело с развертыванием новой переменной.

Этот код работает в Swift 2.x:

let stringA: String! = "Hello"

if stringA != nil
{
    let y = stringA
    let z = y + " world!"
}

В Swift 3:

let stringA: String! = "Hello"

if stringA != nil
{
    let y = stringA
    let z = y + " world!"  // ERROR: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
}

Если вместо этого вы используете String? и разверните его, тогда код будет работать как положено в Swift 2.x и Swift 3:

let stringA: String? = "Hello"

if let stringA = stringA
{
    let y = stringA
    let z = y + " world!"
}

См. Официальное обсуждение этого изменения здесь: SE-0054: отменить неявно неиспользуемый тип

Раздел " Мотивация " этого документа гласит [выделение добавлено мое]:

Тип ImplicitlyUnwrappedOptional ("IUO") является ценным инструментом для импорта API Objective-C, где обнуляемость параметра или возвращаемого типа не определена. Это также представляет удобный механизм для работы через определенные проблемы инициализации в инициализаторах. Тем не менее, IUO являются переходной технологией; они представляют собой простой способ обхода аннотированных API-интерфейсов или отсутствие языковых функций, которые могли бы более элегантно обрабатывать определенные шаблоны кода. Таким образом, мы хотели бы ограничить их использование в будущем и представить более специфические языковые функции, чтобы занять их место. За исключением нескольких конкретных сценариев, опционы всегда безопаснее, и мы хотели бы призвать людей использовать их вместо ННП.

В этом предложении делается попытка ограничить принятие IUO теми местами, где они действительно необходимы, и поставить язык Swift на путь полного удаления неявно развернутых опций из системы, когда другие технологии делают их ненужными. Он также полностью отменяет любое понятие IUO ниже уровня проверки типов компилятора, что существенно упростит реализацию компилятора.

Это дает понять, что разработчики языка считают, что вы должны как можно меньше использовать Implicitly Unwrapped Optionals.

Давайте проясним истинную цель ННП. Они возникли исключительно как устройство для связи с Objective-C. Любой объект из Objective-C может быть nilтеоретически, так что в начале каждый объект, происходящий из Objective-C, был необязательным. Наличие каждого такого Optional быть истинным Optional было слишком разрушительным, поэтому каждый такой объект был неявно развернутым Optional.

Затем, однако, Apple начала ручную настройку API-интерфейсов, чтобы сообщить Swift, может ли на самом деле быть объект, полученный из Objective-C. nil, Этот процесс ручной настройки почти закончен (Swift 3). Таким образом, все, что исходит из Objective-C, теперь является либо обычным объектом, либо обычным необязательным (или, в некоторых случаях, нормальным объектом, который вы должны передать через try чтобы получить).

При этом фактически нет необходимости в неявно развернутых опциях. На данный момент это всего лишь ленивое удобство для программистов. И другие лингвистические устройства пришли, чтобы сделать их ненужными. Например, это боль развернуть опциональный с if let и будет вынужден добавить фигурные скобки уровня, но теперь у нас есть guard let так что вы можете развернуть без добавления фигурных скобок.

Поэтому Apple начала затягивать винты, делая IUO все менее и менее удобным. Например, как справедливо сказал vacawama, они больше не распространяются по назначению. И больше нет такой вещи, как массив IUO (например, [Int!] больше не тип).

Этот процесс будет продолжаться до тех пор, пока не будет нескольких контекстов, в которых IUO является законным. Поэтому лучше всего избавиться от привычки использовать их сейчас.

Другие вопросы по тегам