您可以使用 if 和 let 一起处理可能丢失的值。这些值表示为可选项。可选值要么包含一个值,要么包含 nil 以指示缺少该值。在值的类型后写一个问号 (?) 以将该值标记为可选。
为什么要使用可选值?
Swift 中的 optional 是一种可以保存值或不保存值的类型。可选项是通过将 ?
附加到任何类型来编写的:
var name: String? = "Bertie"
可选项(连同泛型)是最难理解的 Swift 概念之一。由于它们的编写和使用方式,很容易对它们是什么产生错误的认识。将上面的可选项与创建普通字符串进行比较:
var name: String = "Bertie" // No "?" after String
从语法上看,可选字符串与普通字符串非常相似。它不是。可选字符串不是打开了某些“可选”设置的字符串。它不是一种特殊的字符串。 String 和可选 String 是完全不同的类型。
最重要的是要知道:可选项是一种容器。可选字符串是可能包含字符串的容器。可选的 Int 是可能包含 Int 的容器。将可选项视为一种包裹。在您打开它(或用可选语言“解包”)之前,您将不知道它是否包含某些内容。
您可以在 Swift 标准库中看到 how optionals are implemented,方法是在任何 Swift 文件中键入“可选”并 ⌘-单击它。这是定义的重要部分:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Optional 只是一个 enum
,它可以是以下两种情况之一:.none
或 .some
。如果它是 .some
,则有一个关联值,在上面的示例中,它将是 String
“Hello”。可选项使用泛型为关联值提供类型。可选字符串的类型不是 String
,而是 Optional
,或更准确地说是 Optional<String>
。
Swift 对可选项所做的一切都是让阅读和编写代码更加流畅的魔法。不幸的是,这掩盖了它实际工作的方式。稍后我将介绍一些技巧。
注意:我会经常谈论可选变量,但也可以创建可选常量。我用它们的类型标记所有变量,以便更容易理解正在创建的类型类型,但您不必在自己的代码中。
如何创建选项
要创建可选项,请在要包装的类型后附加 ?
。任何类型都可以是可选的,甚至是您自己的自定义类型。类型和 ?
之间不能有空格。
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
使用选项
您可以将可选项与 nil
进行比较以查看它是否具有值:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
这有点令人困惑。这意味着可选是一件事或另一件事。它要么是 nil,要么是“Bob”。这不是真的,可选项不会转换成别的东西。将其与 nil 进行比较是一种使代码更易于阅读的技巧。如果可选项等于 nil,这仅表示枚举当前设置为 .none
。
只有可选项可以为 nil
如果您尝试将非可选变量设置为 nil,则会出现错误。
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
另一种看待可选项的方式是作为普通 Swift 变量的补充。它们是保证有值的变量的对应物。 Swift 是一种谨慎的语言,它讨厌歧义。大多数变量都被定义为非可选变量,但有时这是不可能的。例如,想象一个视图控制器从缓存或网络加载图像。在创建视图控制器时,它可能有也可能没有该图像。无法保证 image 变量的值。在这种情况下,您必须将其设为可选。它以 nil
开头,当检索到图像时,可选值会得到一个值。
使用可选项揭示了程序员的意图。与任何对象都可以为 nil 的 Objective-C 相比,Swift 需要你清楚什么时候可以丢失一个值,什么时候可以保证它存在。
要使用一个可选的,你“解包”它
不能使用可选的 String
代替实际的 String
。要在可选项中使用包装的值,您必须将其解包。解开可选项的最简单方法是在可选项名称后添加 !
。这称为“强制展开”。它返回可选项内的值(作为原始类型),但如果可选项是 nil
,则会导致运行时崩溃。在打开包装之前,您应该确保有一个值。
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
检查和使用可选
因为在展开和使用可选项之前,您应该始终检查 nil,这是一种常见的模式:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
在此模式中,您检查一个值是否存在,然后当您确定它存在时,您将其强制解包为一个临时常量以供使用。因为这是很常见的事情,Swift 提供了一个使用“if let”的快捷方式。这称为“可选绑定”。
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
这将创建一个临时常量(如果将 let
替换为 var
,则为变量),其范围仅在 if 的大括号内。因为必须使用像“unwrappedMealPreference”或“realMealPreference”这样的名称是一种负担,Swift 允许您重用原始变量名称,在括号范围内创建一个临时名称
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
下面是一些代码来演示使用了不同的变量:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
可选绑定通过检查可选是否等于 nil 来工作。如果没有,它会将可选项解包到提供的常量中并执行块。在 Xcode 8.3 及更高版本 (Swift 3.1) 中,尝试打印像这样的可选项将导致无用的警告。使用可选的 debugDescription
使其静音:
print("\(mealPreference.debugDescription)")
什么是可选的?
选项有两个用例:
可能失败的事情(我期待某事,但我什么都没有) 现在什么都不是但以后可能会发生的事情(反之亦然)
一些具体的例子:
可以存在或不存在的属性,例如 Person 类中的 middleName 或配偶
可以返回值或不返回任何值的方法,例如在数组中搜索匹配项
一种可以返回结果或得到错误且不返回任何内容的方法,例如尝试读取文件的内容(通常返回文件的数据)但文件不存在
委托属性,不一定要设置,一般在初始化后设置
对于类中的弱属性。他们指向的东西可以随时设置为零
可能必须释放以回收内存的大型资源
当您需要一种方法来知道何时设置了值(数据尚未加载>数据)而不是使用单独的 dataLoaded Boolean
Objective-C 中不存在可选项,但有一个等效的概念,返回 nil。可以返回对象的方法可以返回 nil。这被认为意味着“没有有效的对象”,并且通常用来表示出现了问题。它仅适用于 Objective-C 对象,不适用于原语或基本 C 类型(枚举、结构)。 Objective-C 通常有专门的类型来表示这些值的缺失(NSNotFound
实际上是 NSIntegerMax
,kCLLocationCoordinate2DInvalid
表示无效坐标,-1
或一些负值也被使用)。编码人员必须了解这些特殊值,因此必须针对每种情况记录和学习它们。如果方法不能将 nil
作为参数,则必须记录在案。在 Objective-C 中,nil
是一个指针,就像所有对象都被定义为指针一样,但 nil
指向一个特定的(零)地址。在 Swift 中,nil
是一个字面量,表示没有某种类型。
与零相比
您过去可以使用任何可选作为 Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
在更新的 Swift 版本中,您必须使用 leatherTrim != nil
。为什么是这样?问题是 Boolean
可以包含在可选中。如果您有这样的 Boolean
:
var ambiguous: Boolean? = false
它有两种“假”,一种没有值,一种有值但值为false
。 Swift 讨厌模棱两可,所以现在您必须始终检查 nil
的可选项。
您可能想知道可选的 Boolean
有什么意义?与其他可选项一样,.none
状态可能表明该值是未知的。网络调用的另一端可能有一些东西需要一些时间来轮询。可选布尔值也称为“Three-Value Booleans”
快速技巧
Swift 使用一些技巧来允许选项工作。考虑这三行普通的可选代码;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
这些行都不应该编译。
第一行使用字符串文字设置一个可选字符串,两种不同的类型。即使这是一个字符串,类型也不同
第二行设置一个可选的String为nil,两种不同的类型
第三行将一个可选字符串与 nil 进行比较,两种不同的类型
我将介绍一些允许这些行工作的选项的实现细节。
创建一个可选的
使用 ?
创建可选的是语法糖,由 Swift 编译器启用。如果你想长期这样做,你可以创建一个这样的可选:
var name: Optional<String> = Optional("Bob")
这将调用 Optional
的第一个初始化程序 public init(_ some: Wrapped)
,它从括号中使用的类型推断出可选的关联类型。
创建和设置可选的更长的方法:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
将可选项设置为 nil
您可以创建一个没有初始值的可选,或者创建一个初始值为 nil
的可选(两者具有相同的结果)。
var name: String?
var name: String? = nil
允许选项等于 nil
由协议 ExpressibleByNilLiteral
(以前称为 NilLiteralConvertible
)启用。可选项是使用 Optional
的第二个初始化程序 public init(nilLiteral: ())
创建的。文档说您不应该将 ExpressibleByNilLiteral
用于除可选项之外的任何内容,因为这会改变代码中 nil 的含义,但可以这样做:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
相同的协议允许您将已创建的可选项设置为 nil
。虽然不推荐,但您可以直接使用 nil 文字初始化器:
var name: Optional<String> = Optional(nilLiteral: ())
将可选项与 nil 进行比较
选项定义了两个特殊的“==”和“!=”运算符,您可以在 Optional
定义中看到它们。第一个 ==
允许您检查是否有任何可选项等于 nil。如果关联的类型相同,则设置为 .none 的两个不同的选项将始终相等。当你与 nil 进行比较时,Swift 在幕后创建了一个相同关联类型的可选项,设置为 .none 然后使用它进行比较。
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
第二个 ==
运算符允许您比较两个选项。两者必须是相同的类型,并且该类型需要符合 Equatable
(允许将事物与常规“==”运算符进行比较的协议)。 Swift(大概)解开这两个值并直接比较它们。它还处理其中一个或两个选项都是 .none
的情况。请注意与 nil
文字比较之间的区别。
此外,它允许您将任何 Equatable
类型与该类型的可选包装进行比较:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
在幕后,Swift 在比较之前将非可选包装为可选。它也适用于文字 (if 23 == numberFromString {
)
我说有两个 ==
运算符,但实际上还有第三个运算符允许您将 nil
放在比较的左侧
if nil == name { ... }
命名选项
没有 Swift 约定来命名可选类型与非可选类型不同。人们避免在名称中添加一些内容以表明它是可选的(如“optionalMiddleName”或“possibleNumberAsString”),并让声明显示它是可选类型。当你想命名一些东西来保存可选的值时,这会变得很困难。 “middleName”这个名字意味着它是一个字符串类型,所以当你从中提取字符串值时,你通常会得到像“actualMiddleName”或“unwrappedMiddleName”或“realMiddleName”这样的名称。使用可选绑定并重用变量名来解决这个问题。
官方定义
从 "The Basics" in the Swift Programming Language:
Swift 还引入了可选类型,用于处理值的缺失。可选项要么说“有一个值,它等于 x”,要么说“根本没有一个值”。可选项类似于在 Objective-C 中使用带指针的 nil,但它们适用于任何类型,而不仅仅是类。 Optional 比 Objective-C 中的 nil 指针更安全、更具表现力,并且是 Swift 许多最强大功能的核心。可选项是 Swift 是一种类型安全语言这一事实的一个例子。 Swift 帮助你清楚你的代码可以使用的值的类型。如果您的部分代码需要一个字符串,类型安全会阻止您错误地传递一个 Int。这使您能够在开发过程中尽早发现并修复错误。
最后,这是一首 1899 年关于可选的诗:
昨天在楼梯上
我遇到了一个不在那里的人
他今天又不在了
我希望,我希望他离开
{ 1}
更多资源:
Swift 编程指南
Swift 中的可选项(中)
WWDC Session 402 “Swift 简介”(大约 14:15 开始)
更多可选提示和技巧
让我们以 NSError
为例,如果没有返回错误,您希望将其设为可选以返回 Nil。如果没有错误,给它赋值是没有意义的..
var error: NSError? = nil
这也允许你有一个默认值。因此,如果函数未传递任何内容,您可以将方法设置为默认值
func doesntEnterNumber(x: Int? = 5) -> Bool {
if (x == 5){
return true
} else {
return false
}
}
nil
,func isNil<T>(t: T?) -> Bool { return t == nil }
也会返回 true
。
return x == 5
? 5有什么特别之处?
在 Swift 中你不能有一个指向 nil
的变量——没有指针,也没有空指针。但是在 API 中,您通常希望能够指示特定类型的值或缺少值 - 例如,我的窗口是否有委托,如果有,它是谁? Optional 是 Swift 的类型安全、内存安全的方法。
我做了一个简短的回答,总结了上述大部分内容,以消除我作为初学者的不确定性:
与Objective-C相反,Swift中没有变量可以包含nil,所以增加了Optional变量类型(变量后缀“?”):
var aString = nil //error
最大的区别是 Optional 变量不直接存储值(就像普通的 Obj-C 变量那样)它们包含两种状态:“有值”或“没有值”:
var aString: String? = "Hello, World!"
aString = nil //correct, now it contains the state "has nil"
也就是说,您可以在不同的情况下检查这些变量:
if let myString = aString? {
println(myString)
}
else {
println("It's nil") // this will print in our case
}
通过使用“!”后缀,您还可以访问包装在其中的值,前提是这些值存在。 (即它不是零):
let aString: String? = "Hello, World!"
// var anotherString: String = aString //error
var anotherString: String = aString!
println(anotherString) //it will print "Hello, World!"
这就是为什么你需要使用“?”和 ”!”并且默认情况下不使用所有这些。 (这是我最大的困惑)
我也同意上面的答案:可选类型不能用作布尔值。
在没有值的目标 C 变量中等于“nil”(也可以使用与 0 和 false 相同的“nil”值),因此可以在条件语句中使用变量(具有值的变量与“TRUE”相同' 而那些没有值的则等于 'FALSE')。
Swift 通过提供“可选值”来提供类型安全。即它可以防止由于分配不同类型的变量而形成的错误。
所以在 Swift 中,只能在条件语句上提供布尔值。
var hw = "Hello World"
在这里,即使 'hw' 是一个字符串,它也不能像在目标 C 中那样在 if 语句中使用。
//This is an error
if hw
{..}
为此,它需要创建为,
var nhw : String? = "Hello World"
//This is correct
if nhw
{..}
可选值允许您显示缺少值。有点像 SQL 中的 NULL 或 Objective-C 中的 NSNull。我想这将是一个改进,因为您甚至可以将其用于“原始”类型。
// Reimplement the Swift standard library's optional type
enum OptionalValue<T> {
case None
case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)”
摘自:Apple Inc.“Swift 编程语言”。电子书。 https://itun.es/gb/jEUH0.l
nil
只是枚举常量 OptionalValue<T>.None
的语法糖(其中 T
是适合您使用 nil
的上下文的类型)。 ?
是 OptionalValue<T>.Some(T)
的快捷方式。
可选意味着 Swift 不完全确定该值是否对应于类型:例如,Int?意味着 Swift 并不完全确定该数字是否为 Int。
要删除它,您可以使用三种方法。
1)如果您完全确定类型,您可以使用感叹号强制解包,如下所示:
// Here is an optional variable:
var age: Int?
// Here is how you would force unwrap it:
var unwrappedAge = age!
如果你强制解开一个可选项并且它等于 nil,你可能会遇到这个崩溃错误:
https://i.stack.imgur.com/DmJJn.png
这不一定安全,所以如果您不确定类型和值,这里有一个方法可以防止崩溃:
方法二和三可以防范这个问题。
2)隐式展开的可选
if let unwrappedAge = age {
// continue in here
}
请注意,展开的类型现在是 Int,而不是 Int?。
3) 守卫声明
guard let unwrappedAge = age else {
// continue in here
}
从这里,您可以继续使用未包装的变量。如果您确定变量的类型,请确保仅强制展开(使用!)。
祝你的项目好运!
当我开始学习 Swift
时,很难理解为什么是可选的。
让我们这样想。让我们考虑一个类 Person
,它有两个属性 name
和 company
。
class Person: NSObject {
var name : String //Person must have a value so its no marked as optional
var companyName : String? ///Company is optional as a person can be unemployed that is nil value is possible
init(name:String,company:String?) {
self.name = name
self.companyName = company
}
}
现在让我们创建几个 Person
的对象
var tom:Person = Person.init(name: "Tom", company: "Apple")//posible
var bob:Person = Person.init(name: "Bob", company:nil) // also Possible because company is marked as optional so we can give Nil
但是我们不能将 Nil
传递给 name
var personWithNoName:Person = Person.init(name: nil, company: nil)
现在让我们谈谈我们为什么使用 optional?
。让我们考虑一种情况,我们希望在公司名称之后添加 Inc
,例如 apple
将是 apple Inc
。我们需要在公司名称后附加 Inc
并打印。
print(tom.companyName+" Inc") ///Error saying optional is not unwrapped.
print(tom.companyName!+" Inc") ///Error Gone..we have forcefully unwrap it which is wrong approach..Will look in Next line
print(bob.companyName!+" Inc") ///Crash!!!because bob has no company and nil can be unwrapped.
现在让我们研究一下为什么 optional 会发生。
if let companyString:String = bob.companyName{///Compiler safely unwrap company if not nil.If nil,no unwrap.
print(companyString+" Inc") //Will never executed and no crash!!!
}
让我们将 bob
替换为 tom
if let companyString:String = tom.companyName{///Compiler safely unwrap company if not nil.If nil,no unwrap.
print(companyString+" Inc") //Will executed and no crash!!!
}
恭喜!我们已经妥善处理了optional?
所以实现点是
如果变量可能为 nil,我们会将其标记为可选如果我们想在代码中的某个地方使用此变量,编译器会提醒您,如果该变量包含 nil,我们需要检查我们是否正确处理了该变量。
谢谢...快乐编码
让我们用下面的代码 Playground 进行实验。我希望能清楚地知道什么是可选的以及使用它的原因。
var sampleString: String? ///Optional, Possible to be nil
sampleString = nil ////perfactly valid as its optional
sampleString = "some value" //Will hold the value
if let value = sampleString{ /// the sampleString is placed into value with auto force upwraped.
print(value+value) ////Sample String merged into Two
}
sampleString = nil // value is nil and the
if let value = sampleString{
print(value + value) ///Will Not execute and safe for nil checking
}
// print(sampleString! + sampleString!) //this line Will crash as + operator can not add nil
可选链是查询和调用当前可能为 nil 的可选的属性、方法和下标的过程。如果可选包含值,则属性、方法或下标调用成功;如果可选项为 nil,则属性、方法或下标调用返回 nil。多个查询可以链接在一起,如果链中的任何链接为零,则整个链都会优雅地失败。
要更深入地了解,请阅读上面的链接。
出色地...
(可选)表示您的变量可能包含 nil 值,而 ! (unwrapper) 表示您的变量在运行时使用(试图从中获取值)时必须具有内存(或值)。
主要区别在于,当可选项为 nil 时,可选项链接会优雅地失败,而当可选项为 nil 时,强制解包会触发运行时错误。
为了反映可以对 nil 值调用可选链接这一事实,可选链接调用的结果始终是可选值,即使您正在查询的属性、方法或下标返回非可选值也是如此。您可以使用此可选返回值来检查可选链接调用是否成功(返回的可选包含一个值),或者由于链中的 nil 值而没有成功(返回的可选值为 nil)。
具体来说,可选链接调用的结果与预期返回值的类型相同,但包装在可选中。通常返回 Int 的属性将返回 Int?通过可选链接访问时。
var defaultNil : Int? // declared variable with default nil value
println(defaultNil) >> nil
var canBeNil : Int? = 4
println(canBeNil) >> optional(4)
canBeNil = nil
println(canBeNil) >> nil
println(canBeNil!) >> // Here nil optional variable is being unwrapped using ! mark (symbol), that will show runtime error. Because a nil optional is being tried to get value using unwrapper
var canNotBeNil : Int! = 4
print(canNotBeNil) >> 4
var cantBeNil : Int = 4
cantBeNil = nil // can't do this as it's not optional and show a compile time error
这是 Apple 开发者委员会提供的详细基本教程:Optional Chaining
Swift 中的 optional 是一种可以保存值或不保存值的类型。可选项是通过附加一个 ?任何类型:
var name: String?
您可以参考此链接深入了解:https://medium.com/@agoiabeladeyemi/optionals-in-swift-2b141f12f870
有很多错误是由人们试图使用未设置的值引起的,有时这可能会导致崩溃,在目标 c 中试图调用 nil 对象引用的方法将被忽略,所以你的一些代码未执行,编译器或编写的代码无法告诉您原因。一个可选参数让您拥有永远不会为零的变量,如果您尝试构建它,编译器可以在您的代码甚至有机会运行之前告诉您,或者您可以决定它适合未定义的对象,然后编译器会在您尝试编写未考虑到这一点的内容时告诉您。
在调用一个可能的 nil 对象的情况下,你可以去
object?.doSomthing()
你已经向编译器和任何阅读你的代码的人明确表示,它的可能对象是 nil 并且什么都不会发生。有时你有几行代码你只想在值存在时出现,所以你可以这样做
if let obj = object {
obj.doSomthing()
doSomethingto(obj)
}
这两个语句仅在对象是某物时才会执行,同样,如果它不是某物,您可能希望停止整个代码块的其余部分
guard let obj = object {
return
}
obj.doSomthing()
doSomethingto(obj)
如果之后的所有内容仅在对象是某物时才适用,则阅读起来会更简单,另一种可能是您想使用默认值
let obj = object ?? <default-object>
obj.doSomthing()
doSomethingto(obj)
现在 obj 将被分配给某些东西,即使它是该类型的默认值
选项在以下情况下很有用它对它做任何事情直到它被设置,我喜欢使用选项的一种方法是告诉我必须做一些事情,或者例如是否已经完成
func eventFired() {
guard timer == nil else { return }
timer = scheduleTimerToCall(method, in: 60)
}
func method() {
doSomthing()
timer = nil
}
此 sudo 代码可以多次调用 eventFired,但只有在第一次调用时才会安排计时器,一旦安排执行,它就会运行一些方法并将计时器设置回 nil,以便可以安排另一个计时器。
一旦您绕过处于未定义状态的变量,您就可以将其用于所有类型的事情。
这是 Swift 中一个等效的可选声明:
var middleName: String?
该声明创建了一个名为 middleName 的 String 类型的变量。 String 变量类型后面的问号 (?) 表示 middleName 变量可以包含一个可以是 String 或 nil 的值。任何查看此代码的人都会立即知道 middleName 可以为 nil。这是自我记录!
如果您没有为可选的常量或变量指定初始值(如上所示),则该值会自动为您设置为 nil。如果您愿意,可以将初始值显式设置为 nil:
var middleName: String? = nil
有关可选的更多详细信息,请阅读以下链接
http://www.iphonelife.com/blog/31369/swift-101-working-swifts-new-optional-values
if myString
不再编译。您需要if myString != nil
。请参阅documentation。