Зачем вам создавать "Implicitly Unwrapped Optional" против создания только регулярной переменной или константы? Если вы знаете, что это может быть успешно развернуто, то зачем создавать необязательный вариант? Например, почему это:
let someString : String! = "this is the string"
будет более полезным vs:
let someString : String = "this is the string"
Если "optionals" указывают, что константе или переменной разрешено иметь "нет значения" ", но" иногда из структуры программ ясно, что опция всегда будет иметь значение после того, как это значение будет установлено первым ", что в первую очередь, сделать его необязательным? Если вы знаете, что необязательный параметр всегда будет иметь значение... Не делает ли это... необязательным?
Рассмотрим случай объекта, который может иметь свойства nil при его создании и настройке, но после этого он неизменен и не равен нулю (NSImage часто обрабатывается таким образом, хотя в его случае он по-прежнему полезен иногда мутировать). Неявно разворачиваемые опционы могли бы значительно улучшить свой код с относительно низкой потерей безопасности (при условии, что одна из гарантий будет сохранена, это будет безопасно).
(Edit) Чтобы быть понятным: регулярные опции всегда предпочтительнее.
Прежде чем я могу описать варианты использования для неявно нераспакованных опций, вы уже должны понять, какие опции и неявно отключенные опционы находятся в Swift. Если вы этого не сделаете, я рекомендую вам сначала прочитать мою статью о опциях
Есть четыре основные причины, по которым можно было бы создать Неявно Unwrapped Необязательный. Все они связаны с определением переменной, которая никогда не будет доступна при nil
, потому что в противном случае компилятор Swift всегда заставит вас явно развернуть необязательный параметр.
Каждая константа члена должна иметь значение по истечении времени инициализации. Иногда константу нельзя инициализировать с ее правильным значением во время инициализации, но при этом можно гарантировать, что она будет иметь значение перед доступом.
Использование необязательной переменной обходит эту проблему, потому что необязательный параметр автоматически инициализируется с помощью nil
, и значение, которое оно в конечном итоге будет содержать, будет по-прежнему неизменным. Однако, это может быть болью, чтобы постоянно разворачивать переменную, которую вы знаете наверняка, не ноль. Неявно Unwrapped Optionals получают те же преимущества, что и Необязательный, с дополнительным преимуществом, которое не нужно явно разворачивать в нем.
Прекрасным примером этого является то, что переменная-член не может быть инициализирована в подклассе UIView до загрузки вида:
class MyView : UIView {
@IBOutlet var button : UIButton!
var buttonOriginalWidth : CGFloat!
override func awakeFromNib() {
self.buttonOriginalWidth = self.button.frame.size.width
}
}
Здесь вы не можете рассчитать исходную ширину кнопки до тех пор, пока не загрузится представление, но вы знаете, что awakeFromNib
будет вызываться перед любым другим методом в представлении (кроме инициализации). Вместо того, чтобы заставлять значение явно разворачиваться бесцельно по всему классу, вы можете объявить его как Неявно Unwrapped Необязательный.
Каждая ссылка на объект в Objective-C является указателем, что означает, что оно может быть nil
. Это означает, что каждое взаимодействие с API Objective-C от Swift должно использовать опцию, где есть ссылка на объект. Вы можете использовать обычный Необязательный в каждом из этих случаев, но если вы точно знаете, что ссылка не будет nil
, вы можете сохранить свой разворачивающийся код, объявив его как Неявно Unwrapped Необязательный.
Хорошим примером этого является UITableViewDataSource
:
EDIT: Пример UITableViewDataSource больше не действителен. Apple уточнил API, и ни один из параметров не является обязательным и не является возвратным значением.
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? { return nil }
Здесь вы знаете, что метод никогда не будет вызываться без tableView
или indexPath
. Было бы пустой тратой времени, чтобы проверить его на nil
. Если бы это был просто Swift API,
не будет объявлять его как необязательный.
nil
Это должно быть крайне редко, но если ваше приложение может буквально не продолжать работать, если переменная nil
при доступе, это будет пустой тратой времени, чтобы протестировать ее для nil
. Обычно, если у вас есть условие, которое должно быть абсолютно верно для вашего приложения, чтобы продолжить работу, вы должны использовать assert
. Необязательно Unwrapped Optional имеет утверждение для nil, встроенного прямо в него.
У Apple есть хотя бы один странный случай с неявно развязанными опциями. Технически все инициализаторы из классов, которые наследуют от NSObject
, возвращают Неявно Unwrapped Optionals. Это связано с тем, что инициализация в Objective-C может возвращать nil
. Это означает, что в некоторых случаях вы все равно захотите проверить результат инициализации для nil
. Прекрасным примером этого является UIImage
, если изображение не существует:
var image : UIImage? = UIImage(named: "NonExistentImage")
if image != nil {
println("image exists")
}
else {
println("image does not exist")
}
Если вы считаете, что существует вероятность того, что ваше изображение не существует, и вы можете изящно обрабатывать этот сценарий, вы можете объявить переменную, фиксирующую инициализацию явно как необязательный, чтобы вы могли проверить ее на nil
. Вы также можете использовать Необязательно Unwrapped Необязательный здесь, но так как вы все равно собираетесь его проверять, лучше использовать обычный Необязательный.
Иногда у вас есть переменная-член, которая никогда не должна быть равна нулю, но при инициализации она не может быть установлена на правильное значение. Одним из решений является использование Неявно Unwrapped Необязательный, но лучший способ - использовать ленивую переменную:
class FileSystemItem {
}
class Directory : FileSystemItem {
lazy var contents : [FileSystemItem] = {
var loadedContents = [FileSystemItem]()
// load contents and append to loadedContents
return loadedContents
}()
}
Теперь переменная-член contents
не инициализируется до первого обращения. Это дает классу шанс войти в правильное состояние перед вычислением начального значения.
Примечание: Это может показаться противоречащим # 1 сверху. Однако существует важное различие. buttonOriginalWidth
выше должен быть установлен во время viewDidLoad, чтобы никто не изменял ширину кнопок до того, как доступ к ресурсу будет выполнен.
По большей части следует избегать неявно отключенных опций, потому что, если их использовать ошибочно, ваше приложение будет аварийно завершено, когда к нему будет доступ, а nil
. Если вы никогда не знаете, может ли переменная быть нулевой, всегда используйте по умолчанию обычный. Развертывание переменной, которая никогда не nil
, конечно, не очень сильно болит.
Неявно развернутые опции полезны для представления свойства как необязательного, когда действительно оно должно быть необязательным под обложками. Это часто необходимо для "связывания узла" между двумя связанными объектами, для каждой из которых нужна ссылка на другую. Это имеет смысл, когда ни одна ссылка не является необязательной, но одна из них должна быть нулевой, пока пара инициализируется.
Например:
// 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) buddy B")
myBuddyB.myBuddyA = self
}
}
var a = A(name:"Big A")
println(a.myBuddyB.name) // prints "Big A buddy B"
Любой экземпляр B
должен всегда иметь действительную ссылку myBuddyA
, поэтому мы не хотим, чтобы пользователь рассматривал ее как необязательный, но нам нужно, чтобы она была необязательной, чтобы мы могли построить B
до мы имеем A
для ссылки.
ОДНАКО! Такое взаимное требование ссылки часто является показателем жесткой связи и плохого дизайна. Если вы обнаружите, что полагаетесь на неявно развернутые варианты, вам, вероятно, стоит подумать о рефакторинге, чтобы устранить кросс-зависимости.
@IBOutlet
Неявно развернутые опции являются прагматичным компромиссом, чтобы сделать работу в гибридной среде, которая должна взаимодействовать с существующими фреймами Cocoa и их соглашениями более приятными, а также позволять поэтапную миграцию в более безопасную парадигму программирования - без нулевых указателей - Swift.
Книга Swift, в главе "Основы" , раздел "Неявно отключенные опционы" :
Неявно развернутые опции полезны, когда подтверждается, что значение optionals существует сразу после того, как опционально определено первым и, безусловно, может быть предположительно существовать в каждой точке после этого. Первичное использование неявно развернутых опций в Swift во время инициализации класса, как описано в Unowned References и Implicitly Unwrapped Additional Properties.
...
Вы можете придумать неявно развернутую опцию, которая дает разрешение на автоматическое разворачивание опциона, когда оно используется. Вместо того, чтобы помещать восклицательный знак после имени опций каждый раз, когда вы его используете, вы помещаете восклицательный знак после того, как тип optionals указывается, когда вы его объявляете.
Это сводится к использованию случаев, когда не nil
-ность свойств устанавливается с помощью соглашения об использовании и не может быть принудительно применена компилятором во время инициализации класса. Например, свойства UIViewController
, которые инициализируются из NIB или Storyboards, где инициализация разделяется на отдельные фазы, но после viewDidLoad()
вы можете предположить, что свойства обычно существуют. В противном случае, чтобы удовлетворить компилятор, вы должны были использовать
принудительная разворачивание,
необязательная привязка
или необязательная цепочка
только для того, чтобы скрыть основную цель кода.
Над частью книги Swift ссылается также на раздел автоматического подсчета ссылок:
Однако существует третий сценарий, в котором оба свойства всегда должны иметь значение, и ни одно свойство никогда не должно быть
nil
после завершения инициализации. В этом случае полезно объединить недопустимое свойство в одном классе с неявным образом развернутым необязательным свойством в другом классе.Это дает оба свойства, чтобы получить доступ непосредственно (без дополнительного разворачивания), как только инициализация завершена, в то же время избегая опорный цикл.
Это сводится к тому, что вы не являетесь языком сбора мусора, поэтому нарушение циклов удержания на вас, как программиста, и неявно развернутые опционы - это инструмент, который скрывает эту причуду.
Что касается "Когда использовать неявно развернутые опции в вашем коде?" вопрос. Как разработчик приложения, вы чаще всего сталкиваетесь с ними в методах подписей библиотек, написанных в Objective-C, которые не имеют возможности выражать необязательные типы.
Из С помощью Swift с Cocoa и Objective-C, раздел Работа с нолем:
Так как Objective-C не гарантирует каких-либо ограничений, то объект Swift делает все классы в типах аргументов и возвращаемых типах необязательными в импортированных API Objective-C. Прежде чем использовать объект Objective-C, вы должны убедиться, что он отсутствует.
В некоторых случаях вы можете быть абсолютно уверены, что метод или свойство Objective-C никогда не возвращает ссылку на объект
nil
. Чтобы сделать объекты в этом специальном сценарии более удобными для работы, Swift импортирует типы объектов как неявно развернутые опции. Неявно развернутые необязательные типы включают все функции безопасности дополнительных типов. Кроме того, вы можете напрямую получить доступ к значению без проверки наnil
или разворачивания его самостоятельно. Когда вы получаете доступ к значению этого типа необязательного типа без его безопасного развертывания вначале, неявно развернутая опция проверяет, отсутствует ли значение. Если значение отсутствует, возникает ошибка времени выполнения. В результате вы всегда должны проверять и разворачивать неявно разворачиваемую опцию самостоятельно, если вы не уверены, что значение не может отсутствовать.
... и далее здесь лежат
Однострочные (или несколько строк) простые примеры не очень хорошо описывают поведение опций - да, если вы объявляете переменную и сразу указываете ее на значение, нет никакого смысла в необязательном.
Самый лучший случай, который я видел до сих пор, - это настройка, которая происходит после инициализации объекта, после чего следует, что "гарантировано" следовать этой настройке, например. в контроллере вида:
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 не решит проблему остановки), поэтому вы сообщаете компилятору, что она существует.
В какой-то степени это снижает уровень безопасности. В любом месте, где у вас есть неявно развернутая опция, это место, где ваше приложение может вылететь из строя, если ваша "гарантия" не всегда выполняется, поэтому это функция используется экономно. Кроме того, при использовании !
все время звучит так, как будто вы кричите, и никто не любит это.
CGSizeZero
может быть хорошим ценностным показателем в реальном использовании. Но что если у вас есть размер, загруженный из пера, который на самом деле может быть нулевым? Тогда использование CGSizeZero
в качестве часового не поможет вам отличить CGSizeZero
значение от нулевого. Более того, это в равной степени относится и к другим типам, загружаемым из nib (или где-либо еще после init
): строкам, ссылкам на подпредставления и т. Д.
Apple дает отличный пример в языке Swift Programming → Автоматический подсчет ссылок → Разрешение сильных ссылочных циклов между экземплярами классов → Неопубликованные ссылки и неявно развернутые дополнительные свойства
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
не будет полностью инициализирован, как описано в Two -фазная инициализация.Чтобы справиться с этим требованием, вы объявляете свойство
capitalCity
Country
как неявно разворачиваемое необязательное свойство.
Если вы точно знаете, что значение возвращается из необязательного вместо nil
, Неявно Unwrapped Optionals использовать для прямого захвата этих значений из опций и необязательных опций не может.
//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
Обоснование неявных опций легче объяснить, сначала взглянув на обоснование принудительной разворачивания.
Принудительное развертывание необязательного (неявного или не), использующего! оператор, означает, что вы уверены, что ваш код не имеет ошибок, а опция уже имеет значение, в котором оно разворачивается. Без! оператор, вы, вероятно, просто утверждаете с необязательной привязкой:
if let value = optionalWhichTotallyHasAValue {
println("\(value)")
} else {
assert(false)
}
что не так хорошо, как
println("\(value!)")
Теперь неявные опциональные опции позволяют вам указывать опцию, которую вы ожидаете всегда иметь значение при распаковке во всех возможных потоках. Таким образом, он просто делает шаг вперед в том, чтобы помочь вам - расслабляя требование написания! разворачивать каждый раз и гарантировать, что время выполнения все равно будет ошибкой, если ваши предположения о потоке ошибочны.
init
(что вы даже не можете реализовать), но они гарантированно будет установлено после awakeFromNib
/ viewDidLoad
.
if someOptional
.