可选类型
在值可能不存在的情况下,您可以使用可选类型。 一个可选类型代表两种可能性:要么它有值,你可以通过解包来访问该值,或者它根本没有值。
注意:
在C或Objective-C中不存在可选类型的概念。 在OC中跟可选类型这
个概念最类似的是一个要么返回对象要么返回nil的方法,其中nil表
示“对象不存在”。然而这种方法仅适用于对象,它并不适用于结构
体、基本类型或枚举。 对于这些类型,OC方法通常返回一个特殊值
(例如NSNotFound)来表示没有值。 这种机制假设了方法调用者知
道存在一个特殊的值,而且会通过这个值来检测方法的结果。 Swift
的可选类型则不需要特殊常量, 就可让您标明任何类型的值都不存在。
这里有一个例子说明了如何使用可选类型来处理不存在的值。 Swift的Int类型有一个初始化方法,它会尝试将一个String类型的值转换成一个Int类型。 但是,并不是每个字符串都可以转换为Int类型。 字符串“123”可以转换为数值123,但字符串“hello,world”明显没有数字可转换为Int。
以下的示例使用这个初始化方法尝试将一个String转换为Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber被转换为类型"Int?",即“可选类型Int”
因为初始化方法可能会失败,所以它返回的是一个可选类型Int,而不是Int。 一个可选类型Int我们用Int?表示,而不是Int。 问号表示这个Int它包含的值是可选的,这意味着它可能包含一些具体的Int值,或者它可能不包含任何值。 (它不能包含任何其他值,例如Bool值或String值,它要么就是一个Int,要么它根本就没有值)
Nil
通过赋值nil,你可以将一个可选类型设置为无值状态:
var serverResponseCode:Int? = 404
// serverResponseCode包含一个具体的Int值,即404
serverResponseCode = nil
// serverResponseCode现在不包含任何值
注意
nil不能和非可选类型的常量和变量一起使用。 如果您的代码中的常
量或变量在某些情况下需要以没有值的状态运行,那您应该始终将其
声明为可选类型。
如果您定义了一个可选类型的变量, 但是不设置默认值,那么该变量的值将自动设置为nil:
var surveyAnswer:String?
// surveyAnswer自动设置为nil
注意
Swift中的nil不同于OC中的nil。 在OC中,nil是指向不存在的对象的
指针。 在Swift中,nil不是指针——它是一个特定类型不存在值的一
种表现形式。 任何数据类型的可选类型都可以设置为nil,而不仅仅
是对象类型。
If和强制解包
您可以通过使用if语句比较可选类型和nil,进而确定这个可选类型是否包含具体的值。您可以使用“等于”运算符(==)或“不等于”运算符(!=)执行此比较。
如果一个可选类型有一个值,即它被认为是“不等于”nil:
if convertedNumber!= nil {
print(“convertedNumber包含一些整数值”)
}
//打印结果:“convertedNumber包含一些整数值”。
一旦您确定可选类型确实有值,您可以通过在可选类型名称的末尾添加感叹号(!)来访问其包含的值。这个感叹号如同在宣称:“我知道这个可选类型肯定有值,请使用它。” 这被称为可选类型的强制解包:
if convertedNumber!= nil {
print(“convertedNumber的值为\(convertedNumber!)”)
}
// 打印结果: “convertedNumber的值为123”
关于if语句的更多信息,请参阅控制流(Control Flow)。
注意
试图使用!访问不存在值的可选类型会触发运行错误。在使用!强制解包
前要始终确保可选类型包含非空的值。
可选绑定
您可以使用可选绑定来确定可选类型是否包含值,如果这样做,你必须把这个绑定的值用作临时常量或变量。
可选绑定可以与if和while语句一起使用,以检查可选类型中的值,并作为这个操作的一部分将该值提取为常量或变量。 if和while语句在控制流(Control Flow)中有更详细的描述。
为if语句编写可选绑定如下所示:
if let constantName = someOptional {
//statements
}
您可以使用可选绑定重写“可选类型”部分的possibleNumber示例,而不是使用强制解包:
if let actualNumber = Int(possibleNumber){
print("\"\(possibleNumber)\"的整数值为\(actualNumber)")
} else {
print("\"\(possibleNumber)\"无法转换为Int类型")
}
// 打印结果: ”123“的整数值为123“
这段代码可以这样理解:
“如果Int(possibleNumber)方法返回的可选Int包含一个值,创建一个名为actualNumber的新常量并设置其值为可选类型包含的值。”
如果转换成功,则常量actualNumber可用于if语句的第一个分支中。它已经通过可选类型中的值初始化了,所以没有必要使用!后缀访问其值。在这个例子中,actualNumber只简单地用于打印转换后的结果。
您可以同时将常量和变量与可选绑定一起使用。如果要在if语句的第一个分支中操作actualNumber的值,您可以编写“if var actualNumber”来替代原来的写法,并且可选类型中包含的值将作为变量而不是常量使用。
您可以在单个if语句中按你的需求包含多个可选绑定和Boolean条件,并以逗号分隔。如果可选绑定中的任何值为nil或任何Boolean条件为false,则整个if语句的条件被认为是false。以下第二段if代码和第一段是等效的:
if let firstNumber = Int("4"), let secondNumber = Int("42"),
firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
// 打印结果: "4 < 42 < 100"
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
}
}
// 打印结果: "4 < 42 < 100”
注意
在if语句中使用可选绑定创建的常量和变量仅在if语句的主体内可用。
相比之下,使用guard语句创建的常量和变量可以在guard语句后面
的代码中使用,如(Early Exit章节)所述。
隐式解包可选类型
如上所述,使用可选类型意味着允许常量或变量可以“无值”。使用if语句可以查看可选类型的值是否存在,并且可以使用可选绑定解包以访问可选类型的值(如果存在)。
在设置了初始值后,有时从程序的结构中可以清楚地看到,可选类型将始终有值。在这种情况下,每次访问可选类型的值时没有必要进行检查和解包,因为我们可以安全地假定任何情况下它都是有值的。
这种可选类型被定义为隐式解包可选类型。通过在想要设置为可选类型的参数后添加一个感叹号(String!),而不是一个问号(String?),你就可以得到一个隐式解包的可选类型。
当可选类型在初始化后确认其值肯定存在,并且可以肯定地假设存在于之后的任何时刻,那么隐式解包可选类型将提供很大的帮助。在Swift中使用隐式解包可选类型主要是在初始化类的时候,如不持有属性和隐式解包可选类型特性(Unowned References and Implicitly Unwrapped Optional Properties)章节所述。
隐式解包可选类型是一个常规选择,但也可以像非可选类型一样使用,而无需在每次访问时进行解包。 以下示例展示了当访问可选String类型与隐式解包可选String类型的一个具体String值时, 两者的差异:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 要求加上!来解包
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要加上!也可以解包
您可以将隐式解包可选类型视为给予一个可选类型权利,在任何它被使用的时候它都可以自动解包。 比起在每次使用可选类型时加上一个感叹号时,您只需要在声明它时在其后添加一个感叹号。
注意
如果一个隐式解包可选类型的值为nil,当您尝试访问其值时则会触发
运行时错误。 其原因与给一个值为空的常规可选类型添加感叹号的情形完全相同。
您仍然可以将隐式解包可选类型视为常规的可选类型,以检查它是否包含一个值:
if assumedString != nil {
print(assumedString)
}
// 打印结果: "An implicitly unwrapped optional string.”
您还可以使用隐式解包可选类型与可选绑定,在单个语句中检查和解包其值:
if let definiteString = assumedString {
print(definiteString)
}
// 打印结果: "An implicitly unwrapped optional string.”
注意
当一个变量在以后有可能为nil时,不要使用隐式解包可选类型。在变
量的生命周期内如果需要检查它是否为nil,请始终使用常规的可选类型。
错误处理
您可以使用错误处理来应对您的程序在执行过程中可能遇到的错误。
与可选类型相反,错误处理可以使用值的存在或不存在来传达一个功能的成功或失败,允许您确定故障的根本原因,如有必要,它还能将错误传送到程序的另一部分。
当函数遇到错误条件时,会引发错误并抛出异常。 那个函数的调用者可以捕获错误并作出适当的响应。
func canThrowAnError() throws {
//这个函数可能会抛出一个错误
}
一个函数通过在其声明中包含throws关键字来说明它可以抛出异常。 当你调用一个可以抛出异常的函数时,你可以在表达式中添加try关键字。
Swift会自动将错误传送到当前区间之外,直到被catch语句处理。
do {
try canThrowAnError()
// no error was thrown
} catch {
// an error was thrown
}
do语句创建一个新的包含范围,它允许将错误传播到一个或多个catch子句。
以下是一个示例,说明如何使用错误处理来响应不同的错误条件:
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
在此示例中,如果没有可用的干净碗筷或缺少任何食材,那么makeASandwich()方法就会抛出错误。 因为makeASandwich()可以引发错误,而且方法的调用被限定在一个try表达式中。 通过在do语句中调用方法,抛出的任何错误将被传送到catch语句进行处理。
如果没有错误发生,那么eatASandwich()方法就会被调用。 如果抛出一个错误并且匹配到SandwichError.outOfCleanDishes,那么washDishes()函数将会被调用。 如果抛出的错误匹配到SandwichError.missingIngredients,则会调用buyGroceries(_ :)方法,并把catch捕获的关联值(String类型)传给它。
错误处理的章节对抛出,捕获和传送错误进行了更详细的描述。
断言(Assertions)和先决条件(Preconditions)
断言和先决条件是会在运行时进行的检查手段。在执行任何其他代码之前,您可以使用它们来确保基本条件已经满足。如果断言或前提条件中的布尔条件为true,则代码照常执行。如果条件为false,那么程序当前处于无效状态;代码会结束执行,您的应用程序也会被终止。
写代码时,使用断言和先决条件您可以表达出你需要的期望条件,因此您可以将它们作为代码的一部分。断言可帮助您在开发过程中发现错误和错误假设,先决条件可帮助您检测发布后的问题。除了在运行时验证您的期望条件外,断言和先决条件也是代码中一种有效的文档形式。与先前在错误处理中讨论的错误条件不同,断言和先决条件不用于可恢复的或期望的错误,因为失败的断言或先决条件表示无效的程序状态,所以无法捕获失败的断言。
使用断言和先决条件并不意味着在您的代码中无效条件就一定不会出现。 然而,使用它们来强制执行有效的数据,会使您的应用程序在错误发生并被强制终止时,更具备可监测性,而且有助于调试。 一旦检测到无效状态,停止执行也有助于将其引起的损坏最小化。
断言和先决条件之间的区别在于被执行的时间点有所不同:断言仅在调试版本中执行,但在调试和发布版本中先决条件都会被执行。 在发布版本中,断言内的条件不再被执行。 这意味着您可以在开发过程中使用尽可能多地使用断言,因为这并不会影响发布版本的性能。
用断言进行调试
通过从Swift标准库调用assert(::file:line:)方法,您写下一个断言。 您将一段文字和一个结果为true或false的表达式赋值给这个方法,如果表达式的结果为false,则显示这段文字。 例如:
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// This assertion fails because -3 is not >= 0.
在这个例子中,如果age> = 0的值为true,那么代码继续执行,即age为正值。 如果age为负值,如上面的代码所示,age> = 0为false,断言失败,终止应用程序。
你可以省略断言发布的文字——例如当它只是一个单调的重复条件。
assert(age >= 0)
如果代码已经检查了条件,则使用assertionFailure(_:file:line:)方法来标明断言失败了。 例如:
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
执行先决条件
当条件有可能为false的时候使用先决条件,但是必须确保您的代码会继续执行。 例如,您可以使用先决条件来检查数组是否越界,或者检查方法是否接收到一个有效的值。
通过调用precondition(::file:line:)方法您可以编写先决条件。 您需要给此方法赋值一个表达式,其计算结果为true或false,如果条件的结果为false,则显示一段文字(也是由你赋值)。 例如:
// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")
您还可以调用preconditionFailure(_:file:line:)方法来标明错误的出现——例如,如果所有输入的有效数据都应该被switch的case语句执行, 但程序执行了switch结构中的default语句。
注意
如果在**-Ounchecked**模式下进行编译,则不执行先决条件。 编译
器假设先决条件始终为真,并优化您的相应的代码。 但是无论如何
设置,**fatalError(_:file:line:)**方法总是不执行,。
在早期开发中你可以使用**fatalError(_:file:line:) **方法给你还未实现
的功能创建存根,例如,使用**fatalError("Unimplemented")**方法作
为方法的实现。 与断言或前提条件不同,因为致命错误从未被优
化,您可以确保程序在执行如果遇到存根实现一定会终止。