Apple's documentation开始:

可以同时使用iflet来处理可能缺少的值.这些值表示为可选值.可选值包含一个值或包含nil以指示缺少该值.在值的类型后写一个问号(?),将该值标记为可选值.

为什么要使用可选值?

推荐答案

Swift中的可选类型是一种既可以保存值也可以不保存值的类型.可选项是通过在任何类型上添加?来编写的:

var name: String? = "Bertie"

Optionals(以及泛型)是最难理解的Swift概念之一.由于它们的书写和使用方式,人们很容易对它们产生错误的认识.将上述选项与创建普通字符串进行比较:

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

从语法上看,可选字符串与普通字符串非常相似.不是.可选字符串不是启用了某些"可选"设置的字符串.这不是一种特殊的弦.字符串和可选字符串是完全不同的类型.

这里有一件最重要的事情要知道:可选的是一种容器.可选字符串是可能包含字符串的容器.可选整数是可能包含整数的容器.将可选整数视为一种Package.在你打开它(或者用optionals的语言"展开")之前,你不知道它包含什么或什么.

通过在任何Swift文件中键入"可选"并⌘-点击它.以下是定义的重要部分:

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

可选的是enum,它可以是两种情况之一:.none.some.如果是.some,则有一个关联值,在上面的示例中,它将是String"Hello".可选的使用泛型为关联的值指定类型.可选字符串的类型不是String,而是Optional,或者更准确地说是Optional<String>.

Swift 对optionals所做的一切都是魔法,可以让读写代码更加流畅.不幸的是,这掩盖了它的实际工作方式.稍后我会介绍一些技巧.

Note:我会经常谈论可选变量,但是创建可选常量也可以.我用它们的类型标记所有变量,以便更容易理解正在创建的类型,但您不必在自己的代码中这样做.


如何创建选项

如果要创建可选的类型,请在?之后进行换行.任何类型都可以是可选的,甚至是您自己的自定义类型.类型和?之间不能有空格.

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,则会出现错误.

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

另一种看待期权的方式是作为普通Swift变量的补充.它们是保证有值的变量的对应项.Swift 是一种谨慎的语言,不喜欢模棱两可.大多数变量被定义为非可选变量,但有时这是不可能的.例如,想象一个视图控制器从缓存或网络加载图像.在创建视图控制器时,它可能有也可能没有该图像.无法保证image变量的值.在这种情况下,您必须将其设置为可选.它从nil开始,当检索图像时,可选的获取一个值.

使用可选选项可以揭示程序员的意图.与Objective-C相比,在Objective-C中,任何对象都可能为零,Swift需要您明确某个值何时会丢失,以及该值何时保证存在.


要使用可选的,您需要"展开"它

可选的String不能代替实际的String.要在可选文件中使用包装的值,必须将其展开.打开可选名称的最简单方法是在可选名称后添加!.这被称为"强制展开".它返回optional中的值(作为原始类型),但如果optional为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)中,try 打印这样的可选文件将导致无用的警告.使用可选的debugDescription将其静音:

print("\(mealPreference.debugDescription)")

什么是可选的?

Optionals有两个用例:

  1. 可能失败的事情(我期待着一些事情,但什么都没有得到)
  2. 现在什么都不是,但以后可能会发生的事情(反之亦然)

一些具体例子:

  • 一个属性可以存在也可以不存在,比如Person类中的middleNamespouse
  • 一种可以返回值或不返回值的方法,如在数组中搜索匹配项
  • 一种方法,可以返回结果或错误,但不返回任何内容,比如试图读取文件的内容(通常返回文件的数据),但文件不存在
  • 委托属性,不必总是设置,通常在初始化后设置
  • 课程中的weak个属性.他们指向的东西可以随时设置为nil
  • 一个可能需要释放以回收内存的大型资源
  • 当您需要一种方法来知道何时设置了值(数据尚未加载>;数据),而不是使用单独的dataLoaded Boolean

Optionals在Objective-C中不存在,但有一个等价的概念,返回nil.可以返回对象的方法可以返回nil.这被理解为"缺少有效对象"的意思,通常用来表示出现了问题.它只适用于Objective-C对象,不适用于原语或基本C类型(枚举、 struct ).Objective-C通常有专门的类型来表示这些值的缺失(NSNotFound表示NSIntegerMaxkCLLocationCoordinate2DInvalid表示无效坐标,-1或一些负值也被使用).编码人员必须了解这些特殊值,因此必须对每种情况进行记录和学习.如果一个方法不能将nil作为一个参数,则必须对此进行记录.在Objective-C中,nil是一个指针,就像所有对象都被定义为指针一样,但nil指向一个特定的(零)地址.在Swift中,nil是一个字面意思,表示没有某种类型.


Comparing to 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)")

Setting an optional to nil

可以创建一个没有初始值的可选项,也可以创建一个初始值为nil的可选项(两者的结果相同).

var name: String?
var name: String? = nil

允许optionals等于nil是由协议ExpressibleByNilLiteral(以前称为NilLiteralConvertible)启用的.可选项是用Optional的第二个初始值设定项public init(nilLiteral: ())创建的.文档中说,除了optionals之外,你不应该使用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 literal初始值设定项:

var name: Optional<String> = Optional(nilLiteral: ())

Comparing an optional to nil

选项定义了两个特殊的"=="和"!="运算符,可以在Optional定义中看到.第一个==允许您判断任何可选值是否等于零.设置为的两个不同选项.如果关联的类型相同,则所有类型都不相等.当创建一个相同类型的Swift时,可 Select 将其与"零"进行比较.没有人会用这个来做比较.

// 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"或"根本没有值".Optionals类似于在Objective-C中对指针使用nil,但它们适用于任何类型,而不仅仅是类.Optionals比Objective-C中的零指针更安全、更具表现力,是Swift许多最强大功能的核心.

Optionals就是一个例子,表明Swift是一种类型安全的语言.Swift帮助您明确代码可以使用的值的类型.如果您希望Int类型的安全代码一部分一部分地传递,则可以防止它.这使您能够在开发过程中尽早捕获并修复错误.


最后,这里有一首1899年的关于optionals的诗:

Yesterday upon the stair
I met a man who wasn’t there
He wasn’t there again today
I wish, I wish he’d go away

Antigonish


更多资源:

Swift相关问答推荐

我应该在自定义存储队列上使用弱self 吗?

如何获取SCNCamera正在查看的网格点(SCNNode)的局部坐标和局部法线?

如何在visionOS中定制悬停效果区域

带DispatchSourceTimer的定时器

SWIFT异步/等待,多个监听程序

SwiftData/PersistentModel.swft:540:致命错误:不支持的关系密钥路径ReferenceWritableKeyPath

Swift计时器未触发

字符串和整数格式的for循环,帮忙!

使用异步收集发布者值

SwiftUI:列表背景为空列表时为黑色

Swift Combine:如何在保留发布者结果顺序的同时进行收集?

使用 WrappingHStack 文本溢出到新行

如何在 ZStack 中单独下移卡片?

试图同时实现两个混合过渡

无法分配给属性:absoluteString是一个只能获取的属性

通用枚举菜单 SwiftUI

如何快速制作虚线?

让我的函数计算数组 Swift 的平均值

用 UIBezierPath 画一条线

如何在 SwiftUI 中的一个视图上显示两个alert ?