Swift中的可选值是什么?


267

Apple的文档中

您可以使用iflet一起使用可能缺少的值。这些值表示为可选值。可选值包含一个值或包含nil以指示该值丢失。?在值的类型后写一个问号(),以将该值标记为可选。

为什么要使用可选值?



1
Optional也可以看作Option / Maybe monad的实现。这个博客在尝试解释什么是困难的概念方面做得很好。
StuartLC 2015年

tldr:“ Swift需要您明确何时可以缺少值以及何时保证它存在。” 来自内文·金(Nevan King)的出色回答
乔纳坦(Jonatan),

Answers:


531

Swift中的可选类型是可以保存值或不保存值的类型。通过将a附加?到任何类型来编写可选内容:

var name: String? = "Bertie"

可选(以及泛型)是最难理解的Swift概念之一。由于它们是如何编写和使用的,很容易对它们是什么有一个错误的认识。比较上面的可选内容与创建普通的String:

var name: String = "Bertie" // No "?" after String

从语法上看,可选字符串与普通字符串非常相似。不是。可选字符串不是打开了某些“可选”设置的字符串。它不是String的特殊种类。字符串和可选字符串是完全不同的类型。

这是要了解的最重要的事情:可选容器是一种容器。可选的String是一个可能包含String的容器。可选的Int是可能包含Int的容器。将可选件视为一种包裹。在您打开它(或用可选语言“解开”)之前,您将不会知道它是否包含某些东西或什么都不包含。

您可以通过在任何Swift文件中键入“ Optional”并在其上单击来查看Swift标准库中如何实现可选。这是定义的重要部分:

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}

可选只是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比较是使代码更易于阅读的技巧。如果可选值等于nil,则仅意味着该枚举当前设置为.none


只有可选参数可以为nil

如果尝试将非可选变量设置为nil,则会出现错误。

var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'

查看可选变量的另一种方法是对普通Swift变量的补充。它们与保证具有值的变量相对应。斯威夫特(Swift)是一种小心翼翼的语言,讨厌歧义。大多数变量被定义为非可选变量,但是有时这是不可能的。例如,假设一个视图控制器从缓存或网络加载图像。创建视图控制器时,它可能有也可能没有该图像。无法保证image变量的值。在这种情况下,您必须将其设为可选。它nil在检索图像时启动,可选参数获得一个值。

使用可选内容可以显示程序员的意图。与Objective-C相比,在其中任何对象都可能为零的情况下,Swift需要您清楚何时可以缺少值以及何时保证该值存在。


要使用可选选项,请“解开”它

可选项String不能代替实际的String。要在可选内容中使用包装的值,您必须将其解包。解开可选项的最简单方法是!在可选名后添加a 。这称为“力展开”。它返回可选值内的值(作为原始类型),但是如果可选值是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)") 
}

这会创建一个临时常量(如果替换letvar,则为变量),其范围仅在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)")

选件有什么用?

可选项目有两个用例:

  1. 可能失败的事情(本来是我期待的东西,但我一无所获)
  2. 事情现在不算什么,但以后可能会发生(反之亦然)

一些具体的例子:

  • 它可以是有或没有在那里,就像一个属性middleNamespousePerson
  • 一种可以返回值或不返回任何值的方法,例如在数组中搜索匹配项
  • 一种可以返回结果或获取错误但不返回任何内容的方法,例如尝试读取文件的内容(通常会返回文件的数据)但该文件不存在
  • 委托属性,通常不必在初始化后进行设置
  • 对于weak类中的属性。他们指出,可以设置为东西nil在任何时间
  • 可能必须释放大量资源才能回收内存
  • 当您需要一种方法来知道何时设置值(尚未加载的数据>数据)而不是使用单独的dataLoaded Boolean

可选在Objective-C中不存在,但是有一个等效的概念,返回nil。可以返回对象的方法可以返回nil。这被认为是“缺少有效的对象”,通常用来表示出了点问题。它仅适用于Objective-C对象,不适用于基元或基本C类型(枚举,结构)。Objective-C通常具有专门的类型来表示这些值的缺失(NSNotFound实际上是NSIntegerMaxkCLLocationCoordinate2DInvalid表示无效的坐标,-1或者也使用某些负值)。编码人员必须了解这些特殊值,因此必须对每种情况进行记录和学习。如果方法不能nil作为参数,则必须记录下来。在Objective-C中nil就像所有对象都定义为指针一样,它nil是一个指针,但指向一个特定的(零)地址。在Swift中,nil是一个文字,表示没有某种类型。


比较 nil

您曾经能够将任何可选选项用作Boolean

let leatherTrim: CarExtras? = nil
if leatherTrim {
    price = price + 1000
}

在较新的Swift版本中,您必须使用leatherTrim != nil。为什么是这样?问题是a Boolean可以包装在可选中。如果您有Boolean这样的话:

var ambiguous: Boolean? = false

它有两种“假”,一种是无值,另一种是有值,而值是false。Swift讨厌模棱两可,因此现在您必须始终对进行检查nil

您可能想知道可选项的意义Boolean是什么?与其他可选项目一样,.none状态可能表明该值尚未确定。网络呼叫的另一端可能有一些内容需要花费一些时间进行轮询。可选的布尔也称为“ 三值布尔值


迅速的把戏

Swift使用一些技巧来允许可选项起作用。考虑一下这三行看起来很普通的可选代码;

var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }

这些行都不应该编译。

  • 第一行使用两种不同的String文字设置可选的String。即使这是一种String类型也不同
  • 第二行将可选的String设置为nil,这是两种不同的类型
  • 第三行将可选字符串与nil(两种不同类型)进行比较

我将介绍一些可选的实现细节,以使这些行起作用。


创建一个可选的

使用?创建可选的语法糖,由斯威夫特编译器启用。如果您想长远地做下去,可以创建一个如下所示的可选内容:

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”之类的名称结尾。使用可选的绑定并重新使用变量名来解决此问题。


官方定义

来自Swift编程语言的“基础知识”

Swift还引入了可选类型,用于处理缺少值的情况。可选的选项是“有一个值,它等于x”或“根本没有值”。可选参数类似于在Objective-C中将nil与指针一起使用,但是它们适用于任何类型,而不仅仅是类。与Objective-C中的nil指针相比,可选选项更安全,更具表达力,并且是Swift最强大功能的核心。

可选的例子说明了Swift是一种类型安全的语言。Swift可以帮助您弄清楚代码可以使用的值的类型。如果代码的一部分需要一个字符串,则类型安全性可防止您误将其传递给Int。这使您能够在开发过程中尽早发现并修复错误。


最后,这是1899年关于可选内容的诗:

昨天在楼梯上,
我遇到了一个不在那儿的人 。我希望他
今天不在那儿
,我希望他能离开

安提哥尼什


更多资源:


5
@KaanDedeoglu不幸的是,史蒂夫是一个可选的人。他在这里,现在不在。

19
if myString不再编译。你需要if myString != nil。请参阅文档

5
最佳和最清楚的解释是?和!在网上找到的Swift中使用。谢谢您
mindbomb 2014年

2
mateo深入解释了Optionals,深入介绍了简单的示例。
iluvatar_GR 2014年

4
谢谢您的解释,它比Apple自己的文档清楚得多。
yesthisisjoe 2015年

16

让我们以的示例为例NSError,如果没有返回错误,则可以选择返回Nil。如果没有错误,则没有必要为其分配值。

var error: NSError? = nil

这也允许您使用默认值。因此,如果函数未传递任何内容,则可以将方法设置为默认值

func doesntEnterNumber(x: Int? = 5) -> Bool {
    if (x == 5){
        return true
    } else {
        return false
    }
}

句子“如果为nil,则任何带有它的表达式的结果也为nil”是完全错误的。即使表达式中存在可选值,也func isNil<T>(t: T?) -> Bool { return t == nil }将返回。truenil
返回true

糟糕的代码示例。你能不能想到更好的东西?为什么不简单return x == 5?5有什么特别之处?
Atomosk

不,两年前,我想不出更好的东西。今天是,但是这是否能说明问题?是。感谢您输入@Atomosk,它确实很有帮助。
约翰·里塞尔瓦托

12

您不能nil在Swift 中指向一个变量-没有指针,也没有空指针。但是在API中,您通常希望能够指示一种特定类型的值或缺少值-例如,我的窗口是否有一个委托,如果是,则是谁?Swift的类型安全,内存安全方式是可选的。


11

我做了一个简短的答案,总结了以上大部分内容,以消除初学者脑海中的不确定性:

与Objective-C相反,在Swift中没有任何变量可以包含nil,因此添加了Optional变量类型(变量后缀为“?”):

    var aString = nil //error

最大的区别是Optional变量不直接存储值(就像普通的Obj-C变量那样),它们包含两个状态:“ 具有值 ”或“ 具有nil ”:

    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
}

通过使用“!” 后缀,也可以访问包装在其中的值(只有当这些值存在时)。(即不是nil):

let aString: String? = "Hello, World!"
// var anotherString: String = aString //error
var anotherString: String = aString!

println(anotherString) //it will print "Hello, World!"

这就是为什么您需要使用“?” 和“!” 并且默认情况下不使用所有这些。(这是我最大的困惑)

我也同意以上答案:可选类型不能用作布尔值


7

在目标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

 {..}

5

可选值使您可以显示没有值。有点像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)

摘录自:苹果公司“ The Swift Programming Language”。iBooks。https://itun.es/gb/jEUH0.l


6
nil只是枚举常量的语法糖OptionalValue<T>.None(其中T的类型适合您所使用的上下文nil)。?是的快捷方式OptionalValue<T>.Some(T)
rickster 2014年

4

可选表示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,则可能会遇到以下崩溃错误:

在此处输入图片说明

这不一定安全,因此如果您不确定类型和值,可以使用以下方法避免崩溃:

方法2和3可以防止此问题。

2)隐式展开的可选

 if let unwrappedAge = age {

 // continue in here

 }

请注意,展开的类型现在是Int,而不是Int?

3)警卫声明

 guard let unwrappedAge = age else { 
   // continue in here
 }

从这里开始,您可以继续使用unwrapped变量。如果您确定变量的类型,请确保仅强制展开(带有!)。

祝您项目顺利!


1

当我开始学习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

但是我们不能传递Nilname

var personWithNoName:Person = Person.init(name: nil, company: nil)

现在让我们谈谈为什么使用optional?。让我们考虑一种情况,我们想要Inc在公司名称之后添加,例如applewill be 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.

现在让我们研究为什么要使用可选内容。

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!!!
}

让我们替换bobtom

if let companyString:String = tom.companyName{///Compiler safely unwrap company if not nil.If nil,no unwrap.

    print(companyString+" Inc") //Will never executed and no crash!!!
}

祝贺!我们已经妥善处理optional?

所以实现点是

  1. 如果可能,我们会将变量标记为可选 nil
  2. 如果我们想在代码编译器的某个地方使用此变量,则会提醒您我们需要检查是否对该变量包含适当的处理nil

谢谢...快乐编码


0

让我们用下面的代码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

0

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html中

可选链接是一个查询和调用当前可能为零的可选属性,方法和下标的过程。如果可选包含值,则属性,方法或下标调用成功;否则,调用成功。如果可选值为nil,则属性,方法或下标调用将返回nil。可以将多个查询链接在一起,如果链中的任何链接为nil,则整个链都会正常失败。

要深入了解,请阅读上面的链接。


0

好...

?(可选)表示您的变量在!时可能包含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开发者委员会详细介绍的基本教程:可选链接



-1

这是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


使用这个,var middleName:字符串!=“”
Gaurav
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.