Зачем создавать «Неявно развернутые дополнительные компоненты», поскольку это означает, что вы знаете, что есть значение?

493

Зачем вам создавать «Неявно развернутый необязательный», а не просто обычную переменную или константу? Если вы знаете, что его можно успешно развернуть, тогда зачем создавать дополнительный файл? Например, почему это так:

let someString: String! = "this is the string"

будет более полезным, чем:

let someString: String = "this is the string"

Если «необязательные параметры указывают, что константе или переменной разрешено иметь« никакого значения »», но «иногда из структуры программы ясно, что необязательный параметр всегда будет иметь значение после того, как это значение будет впервые установлено», в чем смысл сделать его необязательным в первую очередь? Если вы знаете, что необязательное всегда будет иметь значение, разве это не делает его необязательным?

Джонстон
источник

Ответы:

127

Рассмотрим случай объекта, который может иметь свойства nil во время его конструирования и настройки, но впоследствии он будет неизменным и отличным от nil (NSImage часто обрабатывается таким образом, хотя в его случае иногда по-прежнему полезно мутировать). Неявно развернутые опционы будут хорошо очищать его код при относительно низкой потере безопасности (при условии сохранения единой гарантии это будет безопасно).

(Правка) Чтобы было ясно, хотя: обычные опции почти всегда предпочтительнее.

Catfish_Man
источник
459

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

Когда использовать неявно развернутую опцию

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

1. Константа, которую нельзя определить во время инициализации

Каждая константа-член должна иметь значение к моменту завершения инициализации. Иногда константа не может быть инициализирована с ее правильным значением во время инициализации, но она все еще может иметь значение до доступа.

Использование необязательной переменной позволяет обойти эту проблему, поскольку Optional автоматически инициализируется с помощью, nilи значение, которое он в конечном итоге будет содержать, по-прежнему будет неизменным. Тем не менее, может быть больно постоянно разворачивать переменную, которая точно не равна нулю. Неявно развернутые опционные компоненты получают те же преимущества, что и опциональные, с дополнительным преимуществом, которое не требует явной разворачивания его повсюду.

Отличным примером этого является ситуация, когда переменная-член не может быть инициализирована в подклассе UIView до тех пор, пока представление не будет загружено:

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

Здесь вы не можете рассчитать исходную ширину кнопки, пока не загрузится представление, но вы знаете, что awakeFromNibон будет вызван перед любым другим методом в представлении (кроме инициализации). Вместо того, чтобы принудительно бессмысленно развертывать значение во всем вашем классе, вы можете объявить его как Неявно Необязательный Необязательный.

2. Когда ваше приложение не может восстановиться от переменной nil

Это должно быть крайне редко, но если ваше приложение не может продолжать работать, если к переменной nilобращаются, это будет пустой тратой времени на то, чтобы проверить ее nil. Обычно, если у вас есть условие, которое должно быть абсолютно верным, чтобы ваше приложение продолжало работать, вы должны использовать assert. Неявно развернутый необязательный параметр имеет встроенное утверждение для nil. Даже тогда часто бывает полезно развернуть необязательное и использовать более описательный атрибут, если он равен нулю.

Когда не использовать неявно развернутую опцию

1. Ленивые вычисляемые переменные-члены

Иногда у вас есть переменная-член, которая никогда не должна быть nil, но она не может быть установлена ​​на правильное значение во время инициализации. Одним из решений является использование Неявного Unwrapped Optional, но лучше использовать переменную lazy:

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

Теперь переменная-член contentsне инициализируется до первого обращения к ней. Это дает классу шанс войти в правильное состояние перед вычислением начального значения.

Примечание: это может показаться противоречащим № 1 сверху. Однако есть важное различие, которое необходимо сделать. buttonOriginalWidthВыше должны быть установлено во viewDidLoad , чтобы предотвратить любой меняющиеся кнопки ширины до свойства доступа.

2. Везде остальное

По большей части следует избегать неявно развернутых дополнительных компонентов, поскольку при неправильном использовании все приложение будет аварийно завершено при обращении к нему во время nil. Если вы когда-либо не уверены, может ли переменная быть nil, всегда по умолчанию используется обычный Optional. Развертывание переменной, которая никогда не будет, nilконечно, не повредит.

drewag
источник
4
Этот ответ должен быть обновлен до бета-версии 5. Вы больше не можете использовать if someOptional.
Дед Мороз
2
@SantaClaus hasValueопределяется прямо на Необязательный. Я предпочитаю семантику hasValueтем из != nil. Я чувствую, что это гораздо более понятно для начинающих программистов, которые не использовали nilдругие языки. hasValueгораздо логичнее, чем nil.
drewag
2
Похоже, что hasValueего вытащили из бета-версии 6. Эш отложил его обратно ... github.com/AshFurrow/hasValue
Крис Вагнер,
1
@newacct Относительно возвращаемого типа инициализаторов Objc, это больше неявный Неявный Необязательный Необязательный. Поведение, которое вы описали для использования «необязательного», именно то, что будет делать «Неявно развернутый необязательный» (не терпит неудачу, пока не будет получен доступ). Что касается более раннего сбоя программы из-за принудительного развертывания, я согласен с тем, что предпочтительнее использовать необязательные, но это не всегда возможно.
Drewag
1
@confile № Независимо от того, что это собирается появиться в Objective C как указатель (если это было необязательно, неявно развернуто, или необязательно).
drewag
56

Неявно развернутые необязательные параметры полезны для представления свойства как необязательного, когда на самом деле оно должно быть необязательным под прикрытием. Это часто необходимо для «связывания узла» между двумя связанными объектами, каждый из которых нуждается в ссылке на другой. Это имеет смысл, когда ни одна ссылка на самом деле не является обязательной, но одна из них должна быть нулевой, пока пара инициализируется.

Например:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

Любой Bэкземпляр должен всегда иметь допустимую myBuddyAссылку, поэтому мы не хотим, чтобы пользователь рассматривал его как необязательный, но нам нужно, чтобы он был необязательным, чтобы мы могли создать Bдо того, как у нас Aбудет ссылка.

ОДНАКО! Такое требование взаимной ссылки часто свидетельствует о сильной связи и плохой конструкции. Если вы полагаетесь на неявно развернутые опции, вам, вероятно, следует рассмотреть возможность рефакторинга для устранения перекрестных зависимостей.

n8gray
источник
7
Я думаю, что одна из причин, почему они создали эту языковую особенность,@IBOutlet
Jiaaro
11
+1 за предостережение "ОДНАКО". Это может быть не всегда правдой, но это, безусловно, на что-то обратить внимание.
JMD
4
У вас все еще есть сильный ссылочный цикл между A и B. Неявно развернутые опции НЕ создают слабую ссылку. Вам все еще нужно объявить myByddyA или myBuddyB как слабые (вероятно, myBuddyA)
drewag
6
Чтобы еще яснее понять, почему этот ответ неправильный и опасно вводит в заблуждение: неявно развернутые дополнительные компоненты не имеют абсолютно никакого отношения к управлению памятью и не препятствуют сохранению циклов. Однако неявно развернутые необязательные параметры все еще полезны в описанных обстоятельствах для настройки двусторонней ссылки. Так что просто добавив weakдекларацию и удалив "без создания сильного цикла сохранения"
drewag
1
@drewag: Вы правы - я отредактировал ответ, чтобы удалить цикл сохранения. Я намеревался сделать обратную ссылку слабой, но, думаю, это ускользнуло от меня.
n8gray
37

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

В книге Swift, в главе «Основы» , раздел « Неявно развернутые дополнительные компоненты» говорится:

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

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

Это сводится к тому, использовать случаи , когда не- nil-ness свойств устанавливаются посредством использования конвенции, и не может быть насильственным компилятором во время инициализации класса. Например, UIViewControllerсвойства, которые инициализируются из NIB или раскадровок, где инициализация разделена на отдельные фазы, но после viewDidLoad()вы можете предположить, что свойства обычно существуют. В противном случае, чтобы удовлетворить требования компилятора, вы должны были использовать принудительное развертывание , необязательное связывание или необязательное сцепление только для того, чтобы скрыть основное назначение кода.

Выше часть книги Swift относится также к главе « Автоматический подсчет ссылок» :

Однако существует третий сценарий, в котором оба свойства должны всегда иметь значение, и ни одно из свойств не должно быть nilкогда- либо после завершения инициализации. В этом сценарии полезно объединить неизвестное свойство в одном классе с неявно развернутым необязательным свойством в другом классе.

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

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

Это охватывает «Когда использовать неявно развернутые опции в вашем коде?» вопрос. Как разработчик приложения, вы чаще всего встречаетесь с ними в сигнатурах методов библиотек, написанных на Objective-C, которые не имеют возможности выражать необязательные типы.

Из использования Swift с Какао и Objective-C, раздел Работа с nil :

Поскольку Objective-C не дает никаких гарантий того, что объект не равен nil, Swift делает все классы в типах аргументов и возвращаемых типах необязательными в импортированных API Objective-C. Прежде чем использовать объект Objective C, убедитесь, что он не пропущен.

В некоторых случаях вы можете быть абсолютно уверены, что метод или свойство Objective-C никогда не возвращает nilссылку на объект. Чтобы сделать объекты в этом особом сценарии более удобными для работы, Swift импортирует типы объектов как неявно развернутые дополнительные параметры . Неявно развернутые необязательные типы включают в себя все функции безопасности необязательных типов. Кроме того, вы можете получить доступ к значению напрямую, не проверяяnilили развернуть его самостоятельно. Когда вы обращаетесь к значению в этом типе необязательного типа без предварительной безопасной его разворачивания, неявно развернутый необязательный проверяет, отсутствует ли значение. Если значение отсутствует, возникает ошибка во время выполнения. В результате вы должны всегда проверять и развертывать неявно развернутый необязательный файл самостоятельно, если только вы не уверены, что значение не может быть пропущено.

... и за ее пределами драконы

Palimondo
источник
Спасибо за этот подробный ответ. Можете ли вы придумать краткий контрольный список того, когда использовать неявно развернутые необязательные параметры и когда достаточно стандартной переменной?
Hairgami_Master
@Hairgami_Master Я добавил свой собственный ответ со списком и конкретными примерами
drewag
18

Простые примеры, состоящие из одной строки (или нескольких строк), не очень хорошо описывают поведение дополнительных функций - да, если вы объявляете переменную и сразу предоставляете ей значение, в дополнительном элементе нет смысла.

Наилучший случай, который я видел до сих пор, - это настройка, которая происходит после инициализации объекта, за которой следует использование, которое «гарантированно» следует этой настройке, например, в контроллере представления:

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

Мы знаем, что printSizeон будет вызван после загрузки представления - это метод действия, подключенный к элементу управления внутри этого представления, и мы постарались не вызывать его иначе. Таким образом, мы можем сэкономить некоторые дополнительные проверки / привязки с помощью !. Swift не может распознать эту гарантию (по крайней мере, пока Apple не решит проблему остановки), поэтому вы сообщаете компилятору, что она существует.

Это в какой-то степени нарушает безопасность типов. В любом месте, где у вас есть неявно развернутый необязательный параметр, это место, в котором может произойти сбой вашего приложения, если ваша «гарантия» не всегда сохраняется, поэтому эту функцию следует использовать экономно. Кроме того, постоянное использование !звучит так, будто ты кричишь, и это никому не нравится.

rickster
источник
Почему бы просто не инициализировать screenSize для CGSize (height: 0, width: 0) и избавить вас от необходимости кричать переменную каждый раз, когда вы получаете к ней доступ?
Мартин Гордон
Размер, возможно, не был лучшим примером, поскольку CGSizeZeroможет быть хорошим ценностным показателем в реальном использовании. Но что, если у вас есть размер, загруженный из пера, который может фактически быть нулем? Тогда использование CGSizeZeroв качестве часового не поможет вам отличить неустановленное значение от нуля. Более того, это в равной степени относится и к другим типам, загружаемым из nib (или где-либо еще после init): строкам, ссылкам на
подпредставления
2
Частью необязательного в функциональных языках является отсутствие дозорных значений. У вас либо есть значение, либо нет. У вас не должно быть случая, когда у вас есть значение, которое указывает на пропущенное значение.
Уэйн Тэннер
4
Я думаю, что вы неправильно поняли вопрос ОП. ОП не задает вопрос об общем случае для опций, а именно о необходимости / использовании неявно развернутых опций (то есть let foo? = 42, скорее, нет let foo! = 42). Это не относится к этому. (Имейте в виду, что это может быть релевантный ответ об опциях, но не о неявно развернутых опциях, которые являются другим / связанным животным.)
JMD
15

Apple приводит прекрасный пример в языке программирования Swift -> Автоматический подсчет ссылок -> Устранение циклов сильных ссылок между экземплярами классов -> Неизвестные ссылки и Неявно развернутые дополнительные свойства

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

Инициализатор для Cityвызывается из инициализатора для Country. Однако инициализатор для Countryне может пройти selfк Cityинициализатору, пока новый Countryэкземпляр не будет полностью инициализирован, как описано в Двухфазной инициализации .

Чтобы справиться с этим требованием, вы объявляете capitalCityсвойство Countryкак неявно развернутое необязательное свойство.

fujianjin6471
источник
Учебник, упомянутый в этом ответе, находится здесь .
Франклин Ю
3

Обоснование неявных опционов легче объяснить, сначала посмотрев на обоснование принудительного развертывания.

Принудительное развертывание необязательного (неявного или нет), используя! оператор означает, что вы уверены, что в вашем коде нет ошибок, а в дополнительном уже есть значение, в которое он разворачивается. Без ! оператор, вы, вероятно, просто заявите с необязательной привязкой:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

что не так хорошо, как

println("\(value!)")

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

Danra
источник
1
@newacct: non-nil во всех возможных потоках через ваш код не совпадает с non-nil из инициализации родителя (класса / структуры) через освобождение. Интерфейсный Разработчик является классическим примером (но есть много других шаблонов отложенной инициализации): если класс используется когда-либо только из кончика, то выходные переменные не будут установлены init(что вы даже не могли бы реализовать), но они ' повторно гарантированно будет установлен после того, как awakeFromNib/ viewDidLoad.
Рикстер
3

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

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

Так что это разница между использованием

let someString : String! а также let someString : String

enadun
источник
1
Это не отвечает на вопрос ОП. OP знает, что такое неявно развернутый необязательный.
Франклин Ю
0

Я думаю Optional, это плохое имя для этой конструкции, которая смущает многих начинающих.

Другие языки (например, Kotlin и C #) используют этот термин Nullable, и это значительно облегчает его понимание.

Nullableозначает, что вы можете присвоить нулевое значение переменной этого типа. Так что, если это так Nullable<SomeClassType>, вы можете присвоить ему пустые значения, если это просто SomeClassType, вы не можете. Вот как работает Swift.

Зачем их использовать? Ну, иногда вам нужно иметь нулевые значения, вот почему. Например, когда вы знаете, что хотите иметь поле в классе, но не можете назначить его чему-либо, когда создаете экземпляр этого класса, но вы это сделаете позже. Я не буду приводить примеры, потому что люди уже предоставили их здесь. Я просто пишу это, чтобы отдать свои 2 цента.

Кстати, я предлагаю вам посмотреть, как это работает на других языках, таких как Kotlin и C #.

Вот ссылка, объясняющая эту функцию в Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

В других языках, таких как Java и Scala, есть Optionals, но они работают иначе, чем Optionals в Swift, потому что типы Java и Scala все обнуляются по умолчанию.

В общем, я думаю, что эта функция должна была быть названа Nullableв Swift, а не Optional...

Эй, ты
источник
0

Implicitly Unwrapped Optionalэто синтаксический сахар Optional, который не заставляет программиста развернуть переменную. Он может использоваться для переменной, которая не может быть инициализирована во время two-phase initialization processи подразумевает не ноль. Эта переменная ведет себя как не ноль, но на самом деле является необязательной переменной. Хороший пример - интерфейс Интерфейс

Optional обычно предпочтительнее

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
yoAlex5
источник