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

402

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

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

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

let someString : String = "this is the string"

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

Теги:
design
optional

8 ответов

104
Лучший ответ

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

(Edit) Чтобы быть понятным: регулярные опции всегда предпочтительнее.

397

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

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

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

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

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

Использование необязательной переменной обходит эту проблему, потому что необязательный параметр автоматически инициализируется с помощью 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 Необязательный.

2. Взаимодействие с API Objective-C

Каждая ссылка на объект в 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, не будет объявлять его как необязательный.

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

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

4. Инициализаторы NSObject

У 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 Необязательный здесь, но так как вы все равно собираетесь его проверять, лучше использовать обычный Необязательный.

Если не использовать неявно отключенную опцию

1. Lazily Calculated Member Variables

Иногда у вас есть переменная-член, которая никогда не должна быть равна нулю, но при инициализации она не может быть установлена ​​на правильное значение. Одним из решений является использование Неявно 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, чтобы никто не изменял ширину кнопок до того, как доступ к ресурсу будет выполнен.

2. Везде Else

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

  • 4
    Этот ответ должен быть обновлен до бета-версии 5. Вы больше не можете использовать, if someOptional .
  • 0
    @SantaClaus, обновите, спасибо за указание на это
Показать ещё 35 комментариев
53

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

Например:

// 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 для ссылки.

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

  • 7
    Я думаю, что одной из причин, по которой они создали эту языковую функцию, является @IBOutlet
  • 11
    +1 за предостережение "ОДНАКО". Это может быть не всегда правдой, но это, безусловно, на что-то обратить внимание.
Показать ещё 4 комментария
29

Неявно развернутые опции являются прагматичным компромиссом, чтобы сделать работу в гибридной среде, которая должна взаимодействовать с существующими фреймами 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 или разворачивания его самостоятельно. Когда вы получаете доступ к значению этого типа необязательного типа без его безопасного развертывания вначале, неявно развернутая опция проверяет, отсутствует ли значение. Если значение отсутствует, возникает ошибка времени выполнения. В результате вы всегда должны проверять и разворачивать неявно разворачиваемую опцию самостоятельно, если вы не уверены, что значение не может отсутствовать.

... и далее здесь лежат Изображение 1636

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

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

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

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 не решит проблему остановки), поэтому вы сообщаете компилятору, что она существует.

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

  • 0
    Почему бы просто не инициализировать screenSize для CGSize (height: 0, width: 0) и избавить вас от необходимости кричать переменную каждый раз, когда вы получаете к ней доступ?
  • 0
    Размер, возможно, был не лучшим примером, так как CGSizeZero может быть хорошим ценностным показателем в реальном использовании. Но что если у вас есть размер, загруженный из пера, который на самом деле может быть нулевым? Тогда использование CGSizeZero в качестве часового не поможет вам отличить CGSizeZero значение от нулевого. Более того, это в равной степени относится и к другим типам, загружаемым из nib (или где-либо еще после init ): строкам, ссылкам на подпредставления и т. Д.
Показать ещё 2 комментария
14

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 как неявно разворачиваемое необязательное свойство.

  • 0
    Возможный дубликат ответа n8gray .
  • 0
    Учебник, упомянутый в этом ответе, находится здесь .
2

Если вы точно знаете, что значение возвращается из необязательного вместо 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

  • 1
    Это не отвечает на вопрос ОП. OP знает, что такое неявно развернутый необязательный.
2

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

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

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

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

println("\(value!)")

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню