Что такое «развернутая стоимость» в Swift?


155

Я изучаю Swift для iOS 8 / OSX 10.10, следуя этому руководству , и термин « развернутое значение » используется несколько раз, как в этом параграфе (в разделе « Объекты и класс» ):

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

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

Я не понимаю, и искал в Интернете без удачи.

Что это значит?


редактировать

Из ответа Цезария есть небольшая разница между выводом исходного кода и окончательным решением (проверено на игровой площадке):

Оригинальный код

Оригинальный код

Решение Цезари

Решение Цезари

Свойства суперкласса отображаются в выходных данных во втором случае, тогда как в первом случае есть пустой объект.

Разве результат не должен быть одинаковым в обоих случаях?

Связанные вопросы и ответы: Что такое необязательное значение в Swift?


1
Ответ Цезария точен. Кроме того, на iBooks
Даниэль Сторм,

@DanielStormApps Этот iBook является переносной версией связанного учебника в моем вопросе :)
Bigood

Ответы:


289

Во-первых, вы должны понять, что такое необязательный тип. Необязательный тип в основном означает, что переменная может быть nil .

Пример:

var canBeNil : Int? = 4
canBeNil = nil

Знак вопроса указывает на то, что canBeNilможет быть nil.

Это не будет работать:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

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

var canBeNil : Int? = 4
println(canBeNil!)

Ваш код должен выглядеть так:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Sidenote:

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

Пример:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

Итак, альтернативный способ исправить ваш код:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

РЕДАКТИРОВАТЬ:

Разница, которую вы видите, является как раз признаком того факта, что необязательное значение обернуто . Существует еще один слой поверх него. Развернутая версия только показывает прямой объект , потому что, ну, развернутая.

Быстрое сравнение детской площадки:

игровая площадка

В первом и втором случаях объект не разворачивается автоматически, поэтому вы видите два «layer» ( {{...}}), тогда как в третьем случае вы видите только один layer ( {...}), потому что объект автоматически разворачивается.

Разница между первым и вторым двумя случаями заключается в том, что вторые два случая приведут к ошибке времени выполнения, если optionalSquareзадано значение nil. Используя синтаксис в первом случае, вы можете сделать что-то вроде этого:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}

1
Спасибо за ясное объяснение! Код, включенный в мой вопрос, процитирован из учебника Apple (ссылка в вопросе), и я предполагаю, что он должен работать как есть (и он работает). Тем не менее, ваше окончательное и исходное решение дает разные результаты (см. Мое редактирование). Есть мысли?
Bigood

2
Прохладно. Отличное объяснение. Я все еще в замешательстве. Почему я хочу использовать необязательное значение? Когда это полезно? Почему Apple создала такое поведение и синтаксис?
Тодд Леман,

1
Это особенно актуально, когда дело доходит до свойств класса. Swift требует, чтобы все не необязательные методы были инициализированы в init()функции класса. Я рекомендую прочитать главы «Основы» (опциональные обложки), «Инициализация» и «Опциональная цепочка» в книге Swift, выпущенной Apple.
Цезарий Войчик

2
@ToddLehman Apple создала это, чтобы помочь вам написать гораздо более безопасный код. Например, представьте все исключения нулевого указателя в Java, вызывающие сбой программы, потому что кто-то забыл проверить наличие нулевого. Или странное поведение на Objective-C, потому что вы забыли проверить на ноль. С опциональным типом вы не можете забыть иметь дело с нулем. Компилятор заставляет вас иметь дело с этим.
Эрик Энгхайм,

5
@AdamSmith - Исходя из фона Java, я определенно могу видеть использование дополнительных возможностей ... но также (позднее) из фона Objective-C, у меня все еще возникают проблемы с его использованием, потому что Objective- C явно позволяет отправлять сообщения на ноль объектов. Хм. Мне бы очень хотелось, чтобы кто-то написал пример, показывающий, как они помогают сделать код короче и безопаснее, чем эквивалентный код, который их не использует. Я не говорю, что они бесполезны; Я просто говорю, что еще не понял.
Тодд Леман

17

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

Итак, позвольте мне помочь тем коллегам-разработчикам «правильного мышления» (визуального мышления), предоставив другую точку зрения в дополнение к правильному ответу. Вот хорошая аналогия, которая мне очень помогла.

Подарочная упаковка

Думайте об опциях как о подарках на день рождения, которые идут в жесткой, твердой, цветной упаковке.

Вы не знаете, есть ли что-нибудь внутри упаковки, пока не развернете подарок - может быть, внутри ничего нет! Если внутри что-то есть, это может быть еще один подарок, который также обернут, и который также может не содержать ничего . Вы могли бы даже развернуть 100 вложенных подарков, чтобы наконец обнаружить, что нет ничего, кроме упаковки .

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

Что в коробке?! аналогия

Даже после того, как вы развернули переменную, вы все равно похожи на Брэда Питта в последней сцене в SE7EN ( предупреждение : спойлеры и ненормативная лексика и насилие с очень высокой оценкой R), потому что даже после того, как вы развернули настоящее, вы попадаете в следующую ситуацию: теперь у вас есть nil, или коробка, содержащая что-то (но вы не знаете, что).

Вы можете знать тип чего-то . Например, если вы объявили переменную как тип, [Int:Any?]то вы бы знали, что у вас есть (потенциально пустой) словарь с целочисленными индексами, которые дают упакованное содержимое любого старого типа.

Вот почему работа с типами коллекций (словарями и массивами) в Swift может быть довольно сложной.

Дело в точке:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)

хороший 😊 😊😊 😊 😊😊 😊 😊😊
SamSol

люблю аналогию! Итак, есть ли лучший способ развернуть коробки? в вашем примере кода
Дэвид Т.

Кот Шредингера в коробке
Джексонкр

Спасибо, что сбил меня с толку .... Какой программист делает подарок на день рождения, который в любом случае пуст? что-то среднее
delta2flat

@Jacksonkr Или нет.
amsmath

13

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

Все опции в Swift обозначены ?символом. Устанавливая ?после имени типа , в котором вы объявляя в качестве факультативных вы по существу литье это не как тип , в котором находится на рассмотрении ?, но вместо этого в качестве дополнительного типа.


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


Использование опционально

var myString: String?

myString = "foobar"

Это не значит, что вы работаете с типом String. Это означает, что вы работаете с типом String?(String Optional или Optional String). На самом деле, когда вы пытаетесь

print(myString)

во время выполнения консоль отладки напечатает Optional("foobar"). Часть « Optional()» указывает, что эта переменная может иметь или не иметь значение во время выполнения, но именно так и происходит в настоящее время со строкой «foobar». Эта « Optional()» индикация останется, если вы не сделаете то, что называется «развертывание» необязательного значения.

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


Условно Unwrapping проверит, является ли значение в дополнительном nilили нет. Если это не так nil, будет вновь созданная константная переменная, которой будет присвоено значение и развернута в необязательную константу. И оттуда вы можете смело использовать необязательные в ifблоке.

Примечание. Вы можете присвоить своей условно развернутой константе то же имя, что и необязательной переменной, которую вы разворачиваете.

if let myString = myString {
    print(myString)
    // will print "foobar"
}

Условное развёртывание опций - это самый чистый способ получить доступ к значению опциона, потому что если оно содержит значение nil, то все в блоке if let не будет выполняться. Конечно, как любое выражение if, вы можете включить блок else

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

Принудительная распаковка выполняется с помощью так называемого !("взрыва") оператора. Это менее безопасно, но все же позволяет вашему коду компилироваться. Однако всякий раз, когда вы используете оператор bang, вы должны быть уверены на 1000%, что ваша переменная действительно содержит твердое значение перед принудительным развертыванием.

var myString: String?

myString = "foobar"

print(myString!)

Это выше полностью действительный код Swift. Он печатает значение, myStringкоторое было установлено как «foobar». Пользователь увидит foobarнапечатанное в консоли и все. Но давайте предположим, что значение никогда не было установлено:

var myString: String?

print(myString!) 

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


Развертывание с типом литья . Как мы уже говорили ранее, хотя вы unwrappingнеобязательный, вы на самом деле приводите к не необязательному типу, вы также можете привести не необязательный тип к другому типу. Например:

var something: Any?

Где-то в нашем коде переменная somethingбудет установлена ​​с некоторым значением. Может быть, мы используем дженерики или, может быть, есть какая-то другая логика, которая заставит это измениться. Итак, позже в нашем коде мы хотим использовать, somethingно все равно сможем обрабатывать его по-разному, если это другой тип. В этом случае вы захотите использовать asключевое слово, чтобы определить это:

Примечание: asоператор - это то, как вы печатаете приведение в Swift.

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

Обратите внимание на разницу между двумя asключевыми словами. Как и раньше, когда мы принудительно разворачивали опциональный файл, мы использовали для этого !оператор bang. Здесь вы будете делать то же самое, но вместо того, чтобы разыгрывать как необязательное, вы также разыгрываете его Int. И он должен быть в состоянии быть пониженным, как Int, в противном случае, как при использовании оператора взрыва, когда значение nilвашего приложения потерпит крах.

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

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

Тем не менее приведение с помощью as!позволит вашему коду компилироваться. Кастинг с as?- это другая история. На самом деле, as?объявляет вас Intкак совершенно другой тип данных все вместе.

Теперь это Optional(0)

И если вы когда-нибудь пытались сделать домашнее задание, написав

1 + Optional(1) = 2

Ваш учитель математики, вероятно, дал бы вам "F". То же самое со Свифтом. За исключением того, что Свифт предпочел бы вообще не компилировать, а не давать оценку. Потому что в конце дня необязательный может фактически быть ноль .

Безопасность первых детей.


Хорошее объяснение необязательных типов. Помогло прояснить ситуацию для меня.
Tobe_Sta

0

'?' означает необязательное цепочечное выражение, которое означает

«!».
введите описание изображения здесь

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.