Swift是一门开发 iOS、MacOS、WatchOS和TVOS应用的语言。然而,如果你有学过C或者Object-C开发的经验的话,你会发现Swift的很多内容都是你熟悉的。但个人认为Swift的形式很像Python。
Swift包含了C和Object-C的所有基础数据类型,Int表示整型;Double和Foloat表示浮点型;Bool表示布尔类型;String是文本型数据。Swift还提供了三个基本集合类型:Array、Set、Dictionary,这个之后的‘集合类型’一文中会详细介绍。
就像C语言一样,Swift使用变量来进行存储并通过变量名来关联值。在Swift中,广泛的使用着值不可变的变量,它们就是常量,而且比C语言的常量更强大。在Swift中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰的表达你的意图。
除了我们熟悉的类型,Swift还增加了Object-C中没有的高阶数据类型比如说元组Tuple。元组可以让你创建或传递一组数据,比如作为函数的返回值,比如作为函数的返回值时,你可以用一个元组返回多个值。
Swift还添加了可选Optional类型,用于处理值缺失情况。可选表示“有一个值,并且它等于X”或者“没有值”。可选类型有点类似于Object-C中的nil,但是它可以使用在任何类型上,不仅仅是类。可选类型比Object-C中的nil指针更加安全也更加具有表现力,它是Swift许多强大特性的重要组成部分。
Swift是一门类型安全的语言,这意味着Swift可以让你清楚的知道值的类型。如果你的代码需要一个String,类型安全会阻止你不小心传入一个Int。同样的,如果你的代码需要一个String,类型安全会阻止你意外传入一个可选的String。类型安全可以帮助你在开发阶段早期发现并修正错误。
常量和变量
常量和变量把一个名字(例如MaximumNumberOfLoginAttemps或者WelcomeMessage)和一个指定类型的值(比如说数字10或者字符串Hello)关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。
声明常量和变量
常量和变量必须在使用前声明,使用let来声明常量,用var来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数。
let MaximumNumberOfLoginAttempts = 10
var CurrentLoginAttemts = 0
这两段代码可以理解为:
声明一个名为MaximumNumberOfloginAttempts的新常量,并给它一个值10。然后声明一个名为CurrentloginAttempts的新变量并将它的值初始化为0。
在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。
你可以在一行中声明多个常量和多个变量,用逗号隔开:
var x=0.0,y=0.0,z=0.0
类型注解
当你声明变量或者常量的时候可以加上类型注解(type annotation),说明常量或变量中要存储的值的类型。如果要添加类型注解,需要在常量或变量名后面加上一个冒号和一个空格,然后加上类型名称。
var WelcomMessage = "Hello"
这个例子给WelcomMessage变量添加了类型注解,表示这个变量可以存储String类型的值。
var WelcomMessage: String
你可以子啊一行定义中定义多个同种类型的变量,用逗号分隔,并在最后一个变量名之后添加一个类型注解:
var red,green,blue: Double
常量和变量的命名
常量和变量可以包含任何字符,包括Unicode字符:
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "DogAndCow"
常量与变量名不能包含数字符号,箭头,保留的(或非法的)Unicode码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
一旦你将常量或者变量声明为确定的类型,你不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时你也不能将常量与变量进行互转。
对于变量,你可以改现有的值为其他同类型的值,例如:
var FriendWelcom = "Hello!"
FriendWelcom = "Hola"
但常量的值一旦确定就不能修改了。下面这个例子会导致编译时报错。
let LanguageName = "Swift"
LanguageName = "Swift++"
输出常量和变量
你可以使用print(_:separator:teminator:)函数来输出当前常量或者变量的值:
print(FriendWelcom)
print(_:separator:teminator:)是用来输出一个或者多个值到适当输出区的全局函数。如果你用Xcode的话,print(_:separator:teminator)将会输出内容到“console”面板上。separator和reminator参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,改参数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给termination参数—-例如,print(SomeValue,terminator:””)。
注释
单行注释//,多行注释/*……*/。
Swift的多行注释可以嵌套。
/* 这是第一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */
分号
与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号;,当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:
let Cat = "🐱"; print(Cat)
整数
整数就是没有小数部分的数字。整数可以是有符号的或者无符号的。
Swift提供了8、16、32和64位的有符号和无符号整型这些整型类型和C语言的命名方式很类似比如8位无符号整型是UInt8,32位有符号整型就是Int32。就像Swift其他类型一样,整数类型采用大写命名法。
整数范围
你可以访问不同整型的min和max属性来获取对应类型的最大值和最小值:
let MinValue = UInt8.min // 0
let MaxValue = UInt8.max // 256
max和min所传回值的类型是其对应的整数类型(如上例中的UInt8,所传回的类型是UInt8),科用在表达式中相同类型值旁。
整型 Int&UInt
一般来说你不需要指定整数的长度。Swift提供一个特殊的整型Int、无符号整型UInt,长度与当前平台原生字长相同:
- 在32位平台上,
Int和Int32长度相同,UInt和UInt32长度相同。 - 在64位平台上,
Int和Int64长度相同,UInt和UInt64长度相同。
除非你需要特定长度的整型,一般就用Int。这可以提高代码的一致性和可复用性。
浮点数 Float&Double
浮点数是有小数部分的数字。
浮点数类型比整数类型表示的范围更大(科学计数法)。
Swift提供了两种有符号浮点类型:
Double表示64位浮点数。Float表示32位浮点数。
类型安全和类型判断
Swift 是一个类型安全(type safe)的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 String,你绝对不可能不小心传进去一个 Int。
由于 Swift 是类型安全的,所以它会在编译你的代码时进行类型检查(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。
当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 42 和 3.14159 。)
例如,如果你给一个新常量赋值 42 并且没有标明类型,Swift 可以推断出常量类型是 Int ,因为你给它赋的初始值看起来像一个整数:
let MeaningOfLife = 42
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 Double:
let Pi = 3.14159
当推断浮点数的类型时,Swift 总是会选择 Double 而不是 Float。
如果表达式中同时出现了整数和浮点数,会被推断为 Double 类型:
let AnotherPi = 3 + 0.14159
原始值 3 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 Double 类型。
数值型字面量
整数字面量可以被写作:
- 一个十进制数,没有前缀
- 一个二进制数,前缀是
0b - 一个八进制数,前缀是
0o - 一个十六进制数,前缀是
0x
下面的所有整数字面量的十进制值都是 17:
let decimalInteger = 17
let binaryInteger = 0b10001
let octalInteger = 0o21
let hexadecimalInteger = 0x11
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 0x )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 e 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 p 来指定。
如果一个十进制数的指数为 exp,那这个数相当于基数和 10^exp 的乘积:
- 1.25e2 表示 1.25 × 10^2,等于 125.0。
- 1.25e-2 表示 1.25 × 10^-2,等于 0.0125。
如果一个十六进制数的指数为 exp,那这个数相当于基数和 2^exp 的乘积:
- 0xFp2 表示 15 × 2^2,等于 60.0。
- 0xFp-2 表示 15 × 2^-2,等于 3.75。
下面的这些浮点字面量都等于十进制的 12.1875:
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
数值类型转换
通常来讲,即使代码中的整数常量和变量已知非负,也请使用 Int 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。
只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
整数转换
不同整数类型的变量和常量可以存储不同范围的数字。Int8 类型的常量或者变量可以存储的数字范围是 -128~127,而 UInt8 类型的常量或者变量能存储的数字范围是 0~255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:
let CannotBeNegative: UInt8 = -1
let TooBig: Int8 = Int8.max + 1
由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。
要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量 TwoThousand 是 UInt16 类型,然而常量 One 是 UInt8 类型。它们不能直接相加,因为它们类型不同。所以要调用 UInt16(One) 来创建一个新的 UInt16 数字并用 One 的值来初始化,然后使用这个新数字来计算:
let TwoThousand: UInt16 = 2000
let One: UInt8 = 1
let TwoThousandAndOne = TwoThousand + UInt16(One)
现在两个数字的类型都是 UInt16,可以进行相加。目标常量 TwoThousandAndOne 的类型被推断为 UInt16,因为它是两个 UInt16 值的和。
SomeType(ofInitialValue) 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,UInt16 有一个构造器,可以接受一个 UInt8 类型的值,所以这个构造器可以用现有的 UInt8 来创建一个新的 UInt16。注意,你并不能传入任意类型的值,只能传入 UInt16 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型)。
整数和浮点数转换
整数和浮点数的转换必须显式指定类型:
let Three = 3
let PointOneFourOneFiveNine = 0.14159
let Pi = Double(Three) + PointOneFourOneFiveNine
这个例子中,常量 Three 的值被用来创建一个 Double 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。
浮点数到整数的反向转换同样行,整数类型可以用 Double 或者 Float 类型来初始化:
let IntegerPi = Int(Pi)
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4 ,-3.9 会变成 -3。
类型别名
类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用 typealias 关键字来定义类型别名。
当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
typealias AudioSample = UInt16
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
var MaxAmplitudeFound = AudioSample.min // 0
本例中,AudioSample 被定义为 UInt16 的一个别名。因为它是别名,AudioSample.min 实际上是 UInt16.min,所以会给 MaxAmplitudeFound 赋一个初值 0。
布尔值
Swift有一个基本的布尔(boolean)类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift布尔值有两个常量,true和false。
let OrangesAreOrange = true
let TurnipsAreDelicious = false
OrangeAreOrange和TurnipsAreDelicious的类型会被推断为Bool,因为它们的初值是布尔字面量。就和之前的Int以及Double一样,如果你创建变量的时候给它们赋值为true或者false,那你不需要将常量和变量赋值为Bool类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让代码更加简洁并且可读性更高。
当你编写条件语句比如if的时候,布尔值非常有用:
if TurnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
条件语句在之后的控制流中会详细讲解。
如果你在需要使用布尔值的地方使用了非布尔值,Swift的类型安全机制会报错。比如下面的例子会报错:
let I: Int = 1
if I {
}
但是下面这个是合法的:
let I: Int = 1
if I == 1 {
}
I == 1的比较结果是布尔类型,所以第二个例子可以通过类型检查,类似I == 1 这种类型的比较,在下一节基本操作符会详细讲解。
元组
元组(tuples)把多个值组合成一个符合值,元组内的值可以是任何类型,并不要求是相同类型。
下面这个例子中,(404, "Not Found" )是一个描述HTTP状态码(HTTP status code)的元组。HTTP状态码是当你请求网页的时候Web服务器返回的一个特殊值。如果你请求的网页不存在的话就会返回这个状态码。
let Http404Error = (404,"Not Found”)
(404, "Not Found" )元组把一个Int值和一个String值组合起来表示HTTP状态码的两个部分:一个数字和一个可读的描述。这个元组可以被描述为“一个类型为(Int,String)的元组”。
你可以把任意顺序的类型组成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为(Int,Int,Int)或者(String,Bool)或者其他任何你想要的组合的元组。
你可以将一个元组的内容分解(decompose)单独成的常量或者变量,然后你就可以正常使用它们了:
let (StatusCode, StatusMessage) = Http404Error
print("The status code is \(StatusCode)")
print("The status message is \(StatusMessage)")
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_)标记:
let (JustTheStatusCode, _) = Http404Error
print("The status code is \(JustTheStatusCode)")
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
print("The status code is \(Http404Error.0)")
print("The status message is \(Http404Error.1)")
你可以在定义元组的时候给单个元素命名:
let Http200Status = (StatusCode: 200, Description: "OK")
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
print("The status code is \(Http200Status.StatusCode)")
print("The status message is \(Http200Status.Description)")
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。这个会在之后函数参数与返回值一节中讲到。
可选类型
使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 nil,nil 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 NSNotFound)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。
来看一个例子。Swift 的 Int 类型有一种构造器,作用是将一个 String 值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 “123” 可以被转换成数字 123 ,但是字符串 “hello, world” 不行。
下面的例子使用这种构造器来尝试将一个 String 转换成 Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)
nil
你可以给可选变量赋值为 nil 来表示它没有值:
var ServerResponseCode: Int? = 404
ServerResponseCode = nil
nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil:
var SurveyAnswer: String?
Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
if 语句以及强制解析
你可以使用 if 语句和 nil 比较来判断一个可选值是否包含值。你可以使用“相等”(==)或“不等”(!=)来执行比较。
如果可选类型有值,它将不等于 nil:
if ConvertedNumber != nil {
print("convertedNumber contains some integer value.")
}
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):
if ConvertedNumber != nil {
print("convertedNumber has an integer value of \(ConvertedNumber!).")
}
更多关于 if 语句的内容,会在 控制流一节中详细说。
使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值。
可选绑定
使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。
像下面这样在 if 语句中写一个可选绑定:
if let ConstantName = SomeOptional {
Statements
}
你可以像上面这样使用可选绑定来重写 在 可选类型 举出的 possibleNumber 例子:
if let actualNumber = Int(possibleNumber) {
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
这段代码可以被理解为:
“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”
如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。在这个例子中,actualNumber 只被用来输出转换结果。
你可以在可选绑定中使用常量和变量。如果你想在 if 语句的第一个分支中操作 actualNumber 的值,你可以改成 if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。d
你可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 nil,或者任意一个布尔条件为 false,则整个 if 条件判断为 false,这时你就需要使用嵌套 if 条件语句来处理,如下所示:
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
}
}
在 if 条件语句中使用常量和变量来创建一个可选绑定,仅在 if 语句的句中(body)中才能获取到值。相反,在 guard 语句中使用常量和变量来创建一个可选绑定,仅在 guard 语句外且在语句后才能获取到值,会在提前退出一节中详细讲。
隐式解析可选类型
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 if 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考 无主引用以及隐式解析可选属性。
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 String 和隐式解析可选类型 String 之间的区别:
let PossibleString: String? = "An optional string."
let ForcedString: String = PossibleString!
let AssumedString: String! = "An implicitly unwrapped optional string."
let ImplicitString: String = AssumedString
你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:
if AssumedString != nil {
print(AssumedString!)
}
你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:
if let definiteString = assumedString {
print(definiteString)
}
如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。
错误处理
你可以使用 错误处理(error handling) 来应对程序执行中可能会遇到的错误条件。
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。
当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。
func CanThrowAnError() throws {
// 这个函数有可能抛出错误
}
一个函数可以通过在声明中添加 throws 关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置 try 关键词。
do {
try canThrowAnError()
// 没有错误消息抛出
} catch {
// 有一个错误消息抛出
}
一个 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 作为参数。
抛出,捕捉,以及传播错误会在 错误处理 章节详细说明。
使用断言进行调试
你可以调用 Swift 标准库的 assert(::file:line:) 函数来写一个断言。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:
let Age = -3
assert(Age >= 0, "A person's age cannot be less than zero")
在这个例子中,只有 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.")
}
强制执行先决条件
当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。
你可以使用全局 precondition(_:_:file:line:) 函数来写一个先决条件。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:
precondition(index > 0, "Index must be greater than zero.")
你可以调用 preconditionFailure(_:file:line:) 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。
如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,fatalError(_:file:line:) 函数总是中断执行,无论你怎么进行优化设定。
你能使用 fatalError(_:file:line:) 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError(“Unimplemented”)作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。