Зачем создавать "Неявно развернутые дополнительные компоненты", поскольку это означает, что вы знаете, что есть значение?
Зачем вам создавать "Неявно развернутый необязательный", а не просто обычную переменную или константу? Если вы знаете, что его можно успешно развернуть, тогда зачем создавать дополнительный файл? Например, почему это так:
let someString: String! = "this is the string"
будет более полезным, чем:
let someString: String = "this is the string"
Если "необязательные параметры указывают, что константе или переменной разрешено иметь" никакого значения "", но "иногда из структуры программы ясно, что необязательный параметр всегда будет иметь значение после того, как это значение будет впервые установлено", в чем смысл сделать его необязательным в первую очередь? Если вы знаете, что необязательное всегда будет иметь значение, разве это не делает его необязательным?
8 ответов
Рассмотрим случай объекта, который может иметь нулевые свойства во время его конструирования и настройки, но впоследствии он неизменен и не равен нулю (NSImage часто обрабатывается таким образом, хотя в его случае иногда все же полезно мутировать). Неявно развернутые опционы будут хорошо очищать его код при относительно низкой потере безопасности (при условии сохранения единой гарантии это будет безопасно).
(Правка) Чтобы было ясно, хотя: обычные опции почти всегда предпочтительнее.
Прежде чем я смогу описать варианты использования для неявно развернутых необязательных опций, вы должны уже понять, что за необязательные и неявно развернутые необязательные компоненты имеются в Swift. Если вы этого не сделаете, я рекомендую вам сначала прочитать мою статью о дополнительных
Когда использовать неявно развернутую опцию
Есть две основные причины, по которым можно создать неявно развернутый необязательный параметр. Все они связаны с определением переменной, к которой никогда не будет доступа, когда nil
потому что в противном случае компилятор Swift всегда заставит вас явно развернуть Optional.
1. Константа, которую нельзя определить во время инициализации
Каждая константа-член должна иметь значение к моменту завершения инициализации. Иногда константа не может быть инициализирована с ее правильным значением во время инициализации, но она все еще может иметь значение до доступа.
Использование необязательной переменной позволяет обойти эту проблему, потому что необязательный автоматически инициализируется с 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, но ее нельзя установить на правильное значение во время инициализации. Одним из решений является использование Неявно Развернутого Необязательного, но лучший способ - использование отложенной переменной:
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
конечно, не очень больно.
Неявно развернутые необязательные параметры полезны для представления свойства как необязательного, когда на самом деле оно должно быть необязательным под прикрытием. Это часто необходимо для "связывания узла" между двумя связанными объектами, каждый из которых нуждается в ссылке на другой. Это имеет смысл, когда ни одна ссылка на самом деле не является обязательной, но одна из них должна быть нулевой, пока пара инициализируется.
Например:
// 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
ссылаясь на.
ТЕМ НЕ МЕНИЕ! Такое требование взаимной ссылки часто свидетельствует о сильной связи и плохой конструкции. Если вы полагаетесь на неявно развернутые опции, вам, вероятно, следует рассмотреть возможность рефакторинга для устранения перекрестных зависимостей.
Неявно развернутые опции являются прагматическим компромиссом, чтобы сделать работу в гибридной среде, которая должна взаимодействовать с существующими средами Какао и их соглашениями, более приятной, в то же время допуская поэтапную миграцию в более безопасную парадигму программирования - без нулевых указателей - реализуется компилятором Swift.
В книге Swift, в главе "Основы", в разделе " Неявно развернутые дополнительные компоненты" говорится:
Неявно развернутые дополнительные параметры полезны, когда подтверждается, что значение необязательного значения существует сразу после того, как необязательное значение впервые определено, и определенно можно предположить, что оно существует в каждой точке после этого. Основное использование неявно развернутых необязательных опций в Swift происходит во время инициализации класса, как описано в " Неизвестных ссылках" и "Неявно развернутые необязательные свойства".
...
Вы можете думать о неявно развернутом необязательном параметре как о предоставлении разрешения на автоматическое развертывание необязательного параметра при каждом его использовании. Вместо того, чтобы ставить восклицательный знак после имени дополнительного элемента каждый раз, когда вы его используете, вы ставите восклицательный знак после типа дополнительного элемента при его объявлении.
Это сводится к случаям использования, когда nil
-нессность свойств устанавливается соглашением об использовании и не может быть применена компилятором во время инициализации класса. Например, 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
или развернуть его самостоятельно. Когда вы обращаетесь к значению в этом типе необязательного типа без предварительной безопасной его разворачивания, неявно развернутый необязательный проверяет, отсутствует ли значение. Если значение отсутствует, возникает ошибка во время выполнения. В результате вы должны всегда проверять и развертывать неявно развернутый необязательный файл самостоятельно, если только вы не уверены, что значение не может быть пропущено.
... и за ее пределами
Простые примеры, состоящие из одной строки (или нескольких строк), не очень хорошо описывают поведение необязательных параметров - да, если вы объявляете переменную и сразу предоставляете ей значение, в необязательном порядке нет смысла.
Наилучший случай, который я видел до сих пор, - это настройка, которая происходит после инициализации объекта, за которой следует использование, которое "гарантированно" следует этой настройке, например, в контроллере представления:
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 не решит проблему остановки), поэтому вы сообщаете компилятору, что она существует.
Однако это в некоторой степени нарушает безопасность типов. В любом месте, где у вас есть неявно развернутый необязательный параметр, это место, в котором может произойти сбой вашего приложения, если ваша "гарантия" не всегда сохраняется, поэтому эту функцию следует использовать экономно. Кроме того, используя !
все время звучит так, будто ты кричишь, и никому это не нравится.
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
как неявно развернутое необязательное свойство.
Обоснование неявных опционов легче объяснить, если сначала взглянуть на обоснование принудительного развертывания.
Принудительное развертывание необязательного (неявного или нет), используя! оператор означает, что вы уверены, что в вашем коде нет ошибок, а в дополнительном уже есть значение, в которое он разворачивается. Без! оператор, вы, вероятно, просто заявите с необязательной привязкой:
if let value = optionalWhichTotallyHasAValue {
println("\(value)")
} else {
assert(false)
}
что не так хорошо, как
println("\(value!)")
Теперь неявные необязательные параметры позволяют вам выразить наличие необязательного значения, которое, как вы ожидаете, всегда будет иметь значение в развернутом виде во всех возможных потоках. Так что это просто шаг вперед, помогая вам - ослабив требование написания! развертывать каждый раз и гарантировать, что время выполнения все равно будет ошибочным в случае, если ваши предположения о потоке неверны.
Если вы точно знаете, возвращаемое значение из необязательного вместо 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
Implicitly Unwrapped Optional
синтаксический сахар для Optional
это не заставляет программиста разворачивать переменную. Его можно использовать для переменной, которая не может быть инициализирована во времяtwo-phase initialization process
и подразумевает ненулевое значение. Эта переменная ведет себя как ненулевое значение, но фактически является необязательной переменной. Хороший пример - выходы Interface Builder
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
}
Я думаю Optional
- плохое имя для этой конструкции, которая сбивает с толку многих новичков.
Другие языки (например, Kotlin и C#) используют термин Nullable
, и это значительно упрощает понимание этого.
Nullable
означает, что вы можете присвоить переменной этого типа нулевое значение. Так что если этоNullable<SomeClassType>
, вы можете присвоить ему нули, если это просто SomeClassType
, ты не можешь. Так работает Swift.
Зачем их использовать? Ну, иногда нужно иметь нули, вот почему. Например, если вы знаете, что хотите иметь поле в классе, но не можете присвоить его чему-либо при создании экземпляра этого класса, но вы это сделаете позже. Я не буду приводить примеры, потому что люди уже приводили их здесь. Я просто пишу это, чтобы отдать свои 2 цента.
Кстати, я предлагаю вам посмотреть, как это работает на других языках, таких как Kotlin и C#.
Вот ссылка, объясняющая эту функцию в Kotlin:https://kotlinlang.org/docs/reference/null-safety.html
В других языках, таких как Java и Scala, есть Optional
s, но они работают иначе, чем Optional
s в Swift, потому что все типы Java и Scala по умолчанию допускают значение NULL.
В общем, считаю, что эту функцию следовало назвать Nullable
в Swift, а не Optional
...