基础
Swift 是一门开发 iOS,macOS,watchOS,和 tvOS 应用的语言。尽管如此,使用 Swift 开发在许多方面将会跟你使用 C 和 Objective-C 的体验非常类似。
Swift 针对于 C 和 Objective-C 提供了它自己的所有基本类型版本,包括 Int
对应 integers,Double
和 Float
对应浮点值,Bool
对应 Boolean 值,还有 String
对应文本数据。Swift 也提供了三个主要的集合类型, Array
,Set
,和 Dictionary
,如上所述在 Collection Types 这里查看。
与 C 类似,Swift 使用变量来存储,并通过标识名来引用其值。Swift 还广泛使用其值无法更改的变量。这些被称为常量,并且比 C 中的常量更加强大。当你所处理的值不需要更改时,在整个 Swift 使用常量会让你的代码更加安全,更加清晰。
除了熟悉的类型,Swift 还引入了 Objective-C 中没有的高级类型,比如元组。元组可以让你创建与传递值的分组。你可以使用元组将函数中的多个值作为单个复合值返回。
Swift 还引入了可以处理缺省值的可选类型。可选类型表示「要么 有 值,并且等于 X」,「要么 没有 值」。使用可选类型与在 Objective-C 中将指针和 nil 一起使用很相似,但是,可选类型适用于任何类型,不仅仅是类。可选类型不仅比 Objective-C 中的 nil 指针更安全,更具表现力,它还是 Swift 众多强大特性中的核心。
Swift 是一门 类型安全 的语言,这意味着它有助于明确代码中的值的类型。如果代码中需要一个 String
,类型安全可以防止你错误地传递给它一个 Int
。同样的,类型安全可以防止你意外地将一个可选的 String
传递给一个需要非可选 String
的代码片段。在开发过程中,类型安全可以帮你尽早捕捉并修复错误。
常量和变量
常量和变量将名称 ( 例如 maximumNumberOfLoginAttempts
或 welcomeMessage
) 与特定类型的值 ( 例如数字 10
或 字符串 "Hello"
) 相关联。常量 的值一旦被设定就不能更改,而 变量 可以在将来设置为不同的值。
声明常量和变量
常量和变量必须在使用之前声明。使用 let
关键字声明常量,使用 var
关键字声明变量。下面是一个示例,说明如何使用常量和变量来跟踪用户进行的登录尝试次数:
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
此代码可以解读为:
"声明一个名为 maximumNumberOfLoginAttempts
的新常量,并给它一个值 10
,然后声明一个名为 currentLoginAttempt
的新变量,并给它一个初始值 0
。"
在这个例子中,由于允许登录尝试的最大次数不会改变,所以它被声明为常量。由于每次登录失败后当前登录尝试计数器必须递增,所以它被声明为变量。
你可以在一行代码中通过用逗号分隔的方式同时声明多个常量或多个变量,。
var x = 0.0, y = 0.0, z = 0.0
注意
如果代码中的存储值不会改变,请始终使用
let
关键字将它声明为常量。变量仅用于存储需要能被更改的值。
类型注解
当你声明一个常量或者变量的时候,你可以提供一个 类型注解 来明确存储的值的类型。
通过在常量或变量名称的后面放置一个冒号,并再跟一个空格,最后是要使用的类型名称。
下面的例子为 welcomeMessage
的变量提供了一个类型注解,以表明该变量可以存储 String
值。
var welcomeMessage: String
声明中的冒号表示 "...的类型...",所以上面的代码可以被理解成:
『声明一个名为 welcomeMessage
的变量,其类型为 String
』。welcomeMessage
变量现在可以被设置成任何 string
值而不会报错:
welcomeMessage = "Hello"
你可以在一行中定义多个相同类型的变量,使用逗号来分割,在最后的变量名后面加上一个类型注解。
var red, green, blue: Double
注
其实在练习中是很少需要去写类型注解的。如果你在定义一个常量或变量的时候就赋了初始值,Swift
基本上可以推导出这个对象的类型,正如 类型安全和类型推断 描述的一样。在上面的welcomeMessage
例子里,并没有初始值,所以welcomeMessage
变量的类型是通过类型注解来告诉编译器的,而不是提供初始值让编译器推导出来。
命名常量和变量
常量名和变量名可以包含大部分的字符,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
常量名和变量名不能包含空白字符、数学符号、箭头、私有使用(或无效)的 Unicode 代码、点、线和盒绘图字符等。也不能以数字开头,尽管数字可以包括在名称的别处。
一旦声明了某个确定类型的常量或变量,你就不能用相同名字来再次声明变量,或用它来存储不同类型的值。同样你还不能把一个常量换为变量或者把一个变量变为常量。
注意
如果你需要用 Swift 预留关键字来命名常量或变量时,用反时针(``)包围关键字。但是请注意避免使用关键字作为名称,除非你必须这么做。
你可以把现有变量的值改为兼容类型的另一个值,在这个示例里, friendlyWelcome
的值由 "Hello"
变为 "Bonjour!"
:
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 现在是 "Bonjour!"
和变量不同,常量的值被设定后不可更改,尝试修改则会在编译时报错:
let languageName = "Swift"
languageName = "Swift++"
// 编译错误: languageName 不可修改.
打印常量与变量
你可以通过print(_:separator:terminator:)
方法来打印一个常量或变量当前的值。
print(friendlyWelcome)
// Prints "Bonjour!"
print(_:separator:terminator:)
方法是一个全局的方法,它可以将一个或多个值合适的输出。例如,在 Xcode
中 print(_:separator:terminator:)
方法会在 Xcode
的 控制台
区域中打印出结果。separator
和 terminator
参数有默认的值,所以你在调用这个方法的时候可以忽略掉它们。
默认的,这个方法会添加一个换行符来终止它打印的行。
要打印一个没有换行符的值,传递一个空的字符串作为 terminator
参数的值 --- 例如,print(someValue, terminator: "")
。有关具体的参数的默认值的信息,请查看Default Parameter Values。
Swift
使用 字符串插值 的方式来包含常量或变量的名称作为一个占位符并生成一个更长的字符串,然后提醒编译器使用当前常量和变量的值来替换。把名称包含在括号之中,并在左括号之前使用反斜杠来转义:
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// Prints "The current value of friendlyWelcome is Bonjour!"
注意
所有你可以使用的字符串插值选项内容都可以在 String Interpolation 查看.
注释
使用注释来包裹代码中不可执行的文本,来作为一个笔记或对自己的提醒。当代码开始编译时,Swift
编译器会忽略掉这些注释。Swift
中的注释与 C
中的注释十分相似。单行的注释以两个正斜杠开始(//
):
// 这是一个注释.
多行的注释以一个正斜杠后面跟随一个星号( /*
)开始,以一个星号后面跟随一个正斜杠( */
)结束。
/* 这也是一个注释
但是是多行的. */
与 C
中的多行注释不一样的是,Swift
中的多行注释可以相互嵌套使用。你可以编写多行注释,先是第一个多行注释块的开始,然后是第二个多行注释块的开始,然后是第二个多行注释块的结束,最后是第一个多行注释快的结束。
/* 这是第一个多行注释的开始
/* 这是第二个,嵌套的多行注释 */
这是第一个多行注释的结束. */
嵌套的多行注释能够让你更快、更容易的注释大型代码块,即使代码块里面已经有多行注释了。
分号
与很多其他编程语言不同,Swift 不要求你在代码中每一条语句后面都使用分号 (;
) 来结尾,当然你可以根据自己的喜好仍旧这么做,不过,当你试图在同一行内编写多条独立语句的时候,任意两条语句之间是必须用分号分隔开的:
let cat = "🐱"; print(cat)
// 打印 "🐱"
整数
整数就是没有小数部分的数字,例如 42
和 -23
。 整数可以是有符号的 (正数, 零, 或者 负数),也可以是无符号的 (正数 或者 零) 。
Swift 提供了 8 位,16 位,32 位和 64 位不同格式的有符号和无符号整数类型。这些整数的命名方式与 C 语言非常类似,比如一个 8 位的无符号的整数类型是 UInt8
,而一个 32 位有符号整数的类型是 Int32
。和其他 Swift 中的类型一样,这些整数类型都采用大驼峰命名法命名。
Integer
的边界
你可以通过 min
和 max
属性来获取每个 integer
类型的最大值和最小值。
let minValue = UInt8.min // 最小值为 0,类型为 `UInt8`
let maxValue = UInt8.max // 最大值为 255,类型为 `UInt8`
这些属性的值是有适当大小的数字类型(比如上面的 UInt8
)的,因此可以在表达式中与其他类型的值一起使用。
Int
在大多数情况下,你不需要去选择一个特定大小的 integer
类型。Swift
提供了一个附加的 integer
类型,Int
。Int
的大小与当前平台的原生 Word
大小相同。
- 32位系统,
Int
与Int32
大小相同 - 64位系统,
Int
与Int64
大小相同
除非你必须要确定一个 integer
的具体大小,否则一般情况下只需要使用 Int
类型。这将有助于代码一致性与互操作性。即使在32位系统中,Int
可以存储 -2,147,483,648
和 2,147,483,648
之间的任何整型值,这个范围对于大多数整型来说已经足够大了。
UInt
Swift
还提供了一个无符号的整型类型,UInt
,该类型与当前操作系统的原生 Word
大小相同:
- 32位平台下,
UInt
和UInt32
大小相同 - 64为平台下,
UInt
和UInt64
大小相同
注意
只有当你明确的需要一个与操作系统原生
Word
大小相同的无符号整型类型时才应该去使用UInt
。如果不需要的话,Int
更适合,即使要存储的值是非负的。对整型的数值一致性的使用Int
有助于代码的互操作性,同时也避免了不同数值类型之间转换的需要。而整型的类型推断,如中所述 Type Safety and Type Inference.
浮点数
浮点数 是有小数点的数字,比如 3.14159
, 0.1
和 -273.15
。
浮点类型相比于整型可以表示一个更大的数值区间,可以存储相比于 Int
更大和更小的数值。Swift
提供了两种有符号浮点数类型:
Double
表示64位的浮点数类型Float
表示32位的浮点数类型
注意
Double
类型可以精确到小数点后15位,而Float
类型只有6位。
选择合适的浮点数类型取决于你代码中使用到的值的性质和范围。如果两种类型都能使用的情况下,优先使用Double
类型 。
类型安全与类型推断
Swift 是一门类型安全的语言。类型安全的语言可以让你清楚地知道代码可以处理的值的类型。如果你的一部分代码需要获得 String
,你就不能错误的传 Int
给它。
因为 Swift 是类型安全的, 它在编译代码的时候会进行类型检查,任何不匹配的类型都会被标记为错误。这会让你能够在开发过程中尽早的发现错误并修复它。
当你操作不同类型的值时,类型检查能够帮你避免错误。 但是,这并不意味着你必须指定你声明的每一个常量和变量的类型。 如果你没有为所需要的值进行类型声明,Swift 会使用类型判断的功能推断出合适的类型。通过检查你给变量赋的值,类型推断能够在编译阶段自动的推断出值的类型。
因为有了类型推断,Swift 和 C 以及 Objective-C 相比,只需要少量的类型声明。其实常量和变量仍然需要明确的类型,但是大部分的声明工作 Swift 会帮你做。
在你声明一个变量或者常量并设定一个初始值的时候,类型推断就显得格外有用。它通常在你声明常量或变量并赋予了一个字面量(文本)时就已完成。(字面量就是直接出现在代码中的值,比如下边代码中的 42
和 3.14159
。)
举个例子,如果你给一个新的常量指定了一个字面量 42
,并且没有说明它是什么类型的,Swift 会推断你希望这个常量为 Int
类型,因为你使用了一个看起来像整数的数字来初始化它:
let meaningOfLife = 42
// meaningOfLife 被推断为 Int 类型
同样,如果你没有为一个浮点值的字面量设定类型,Swift 会推断你想创建一个 Double
类型:
let pi = 3.14159
// pi 被推断为 Double 类型
Swift 在推断浮点值的时候始终会选择 Double
(而不是Float
)。
如果你在一个表达式中组合使用了整数和浮点数,Double
类型将会从内容中被推断出来:
let anotherPi = 3 + 0.14159
// anotherPi 也同样会被推断为 Double 类型
字面量 3
本身并没有明确的类型,但因为有一个浮点类型的字面量作为加法的一部分,所以这个类型就被推断为 Double
。
数值型字面量
整数型字面量可以写作:
- 十进制数,没有前缀
- 二进制数,前缀为
0b
- 八进制数,前缀为
0o
- 十六进制数,前缀为
0x
下面这些整数字面量的十进制值都是 17
:
let decimalInteger = 17
let binaryInteger = 0b10001 // 17 的二进制值
let octalInteger = 0o21 // 17 的八进制值
let hexadecimalInteger = 0x11 // 17 的十六进制值
浮点型字面量可以是十进制(没有前缀)或者是十六进制(带有0x前缀)。小数点两边必须有至少一个十进制数(或者十六进制数)。十进制浮点数也可以有一个可选的指数,用大写或小写的 e
表示;十六进制的浮点数必须含有指数,用大写或小写的 p
来表示。
十进制数与 exp
的指数, 结果就等于基数乘以 10^exp^:
1.25e2
意味着 1.25 x 10^2^,或者125.0
。1.25e-2
意味着 1.25 x 10^-2^, 或者0.0125
。
对于一个指数为 exp
的十六进制数,它的结果为基数乘以2^exp:
0xFp2
意味着 15 x 2^2,或者60.0
。0xFp-2
意味着 15 x 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
Numeric Type Conversion
对代码中所有通用的整数常量和变量使用"Int"类型,即便已经知道他们是非负的。在日常情况下使用默认整数类型意味着整数常量和整数变量在代码中可相互转换,并且与推断的整数字面类型相匹配。
仅在特定任务时才会使用其他的整数类型,因为来自外部源的显示大小的数据,或性能、内存使用或其他必要的优化。在这些情况下,使用显示大小的类型有助于捕捉异常并隐式地记录所使用的数据的类型。
整型转换
每种整型类型中的常量或者变量存储的数字大小范围是不一样的。一个 Int8
类型的常量或变量可以存储 -128
和 127
之间的数字,而一个 UInt8
类型的常量或变量可以存储 0
到 255
之间的数字。
当编译时,不符合整型大小的常量或变量的数字将会报错:
let cannotBeNegative: UInt8 = -1
// `UInt8` 不能够存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1
// `Int8` 不能存储比它的最大值还要大的值
// 所以同样也会报错
因为每种数字类型都可以存储不同范围的值,你必须根据情况选择合适的类型转换策略。这种转换策略可以防止类型转换的错误,并且让代码中类型转换的意图更加明显。
从一种整型类型转换到另一种,使用当前的值初始化出一个新的整型类型的值。在下面的例子中,twoThousand
常量是 UInt16
类型,而 one
常量是 UInt8
类型。它们不能够直接相加,因为它们不是相同的数据类型。相反的,下面的例子调用 UInt16(one)
来创建一个值为 one
的新的 UInt16
对象,然后用这个值替换原本的值。
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因为加法的两边现在都是 UInt16
类型,所以相加是成立的。输出的常量 twoThousandAndOne
会被推导出为 UInt16
类型,因为这是两个 UInt16
相加的结果。
SomeType(ofInitialValue)
是 Swift
中初始化一个对象的默认方式,在这个过程中需要传入一个初始值。而在底层实现中,UInt16
有一个接收 UInt8
类型的初始化构造器,所以这个构造器是用来转换 UInt8
类型 到 UInt16
类型 的。你不能在这传入 任何 类型,因为必须是 UInt16
初始化构造器允许的类型才可以。扩展现有类型的初始化构造器,让其接收新的类型(包括你自己自定义的类型)的内容在 Extensions 中可以找到。
整型与浮点型的类型转换
整型与浮点型数字之间的类型转换必须是显式的:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159, 然后会被 Swift 推导为 Double 类型
这里 three
常量的值被用来创建一个新的 Double
类型的值,所以加法两边的值是相同的数据类型。如果没有类型转换,这两个数的相加就会报错。
浮点数转整型同样也需要显式转换。一个整型可以以 Double
或 Float
类型初始化:
let integerPi = Int(pi)
// integerPi 等于 3, 然后会被 Swift 推导为 Int 类型
当用这种方式初始化一个新的整型值的时候,浮点数总是会被截断。比如说 4.75
会转换成 4
,而 -3.9
会转换成 -3
。
注意
组合数字常量与变量的规则不同于数字字面量的规则。字面量
3
可以直接与字面量0.14159
相加,因为字面量本身并没有一个显式的类型,只有当编译器评估它们的值的时候,它们的类型才会被推导出来。
类型别名
类型别名 就是给现有类型定义了一个另外的名字。你可以使用 typealias
关键字来声明类型别名。
当你想给现有类型取一个更合适的名字的时候,类型别名就非常有用。
例如处理特定长度的外部数据:
typealias AudioSample = UInt16
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0
这里,AudioSample
被定义为 UInt16
的别名。因为这是个别名,所以对 AudioSample.min
的调用实际上是对 UInt16.min
的调用,所以会给 maxAmplitudeFound
变量赋予一个为 0
的初始值。
布尔值
Swift 有一个基础 布尔 类型 Bool
. 布尔值也被称为 逻辑值, 因为它们只能是真或假。 Swift 提供两个布尔常量, true
和 false
:
let orangesAreOrange = true
let turnipsAreDelicious = false
orangesAreOrange
和 turnipsAreDelicious
的类型被推断为 Bool
, 因为它们是用布尔值初始化的。 和上面的 Int
和 Double
一样, 如果创建变量时把它们设置为 true
或 false
,那么就不必用 Bool
来声明它。 使用已知类型的其他值来初始化常量或变量,类型推导使得 Swift 代码更加简洁和易读。
当你使用条件语句时,布尔类型的值将变得特别有用,比如 if
语句:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// Prints "Eww, turnips are horrible."
关于条件语句 if
的更多细节在 Control Flow.
Swift 的类型安全防止非布尔类型的值被替换为 Bool
. 下面例子将会在编译时报错:
let i = 1
if i {
// this example will not compile, and will report an error
}
但是下面的例子是有效的:
let i = 1
if i == 1 {
// this example will compile successfully
}
i == 1
的比较结果是 Bool
类型,所以第二个例子在编译时能够通过检查。更多关于 i == 1
的讨论: Basic Operators.
与 Swift 中类型安全的其他示例一样,这种方法避免了意外错误,并确保特定代码段的意图总是清晰的。
元组
元组 将多个值组合在一起成为一个复合值。元组里面的值可以是任何类型,不需要是相同的类型。
下面的例子中,(404, "Not Found")
是一个描述了 HTTP 状态码 的元组。一个 HTTP
状态码是
当你请求一个网页时服务器返回的一个特殊值。如果你请求的网页不存在的话,就会返回状态码 404 Not Found
。
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String), 等于 (404, "Not Found")
(404, "Not Found")
元组将一个 Int
和一个 String
组合起来表示 HTTP
状态码的两个部分:一个数字和一个人类可读的描述。可以被描述为 「一个类型为 (Int, String)
的元组」。
你可以把任意顺序的类型组成一个元组,这个元组可以包含任何你想要的类型。只要你想,你可以创建一个 (Int, Int, Int)
或 (String, Bool)
,甚至是任何你需要的类型的元组。
你可以将一个元组的内容 分解 为单独的常量或变量,然后你就可以正常使用它们了:
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 打印 "The status code is 404"
print("The status message is \(statusMessage)")
// 打印 "The status message is Not Found"
如果你只需要元组里的一部分值的话,分解元组的时候使用一个下划线(_
) 来忽略元组里面的值:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 打印 "The status code is 404"
此外,使用从零开始的下标来访问元组里单个元素的值:
print("The status code is \(http404Error.0)")
// 打印 "The status code is 404"
print("The status message is \(http404Error.1)")
// 打印 "The status message is Not Found"
定义元组时,你可以为元组中的单个元素命名:
let http200Status = (statusCode: 200, description: "OK")
如果你在元组里为元素命名了,那么你就可以通过元素名称去获取元素的值:
print("The status code is \(http200Status.statusCode)")
// 打印 "The status code is 200"
print("The status message is \(http200Status.description)")
// 打印 "The status message is OK"
元组作为函数的返回值的时候十分有用。一个尝试获取一个网页的函数可能会返回一个 (Int, String)
类型来表示结果的成功或失败。相比于返回一个类型的单个值作为结果,通过返回包含两个不同类型值的一个元组作为返回值,这个函数让自己的返回值提供了更多有用的信息。更多信息,参考 函数参数与返回值
注意
元组在临时组织的值的时候很有用。元组并不适合用于创建复杂数据结构。如果你的数据结构
比较持久而不是临时使用的话,使用类或者结构体,而不是元组。更多信息,参考 结构体和类.
可选类型
使用 可选类型 来处理值可能缺省的情况。一个可选类型代表了两种可能:要么 有 值,然后你就可以通过解包来获取值;要么 没有 值。
注意
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)
// convertedNumber 被推导为 "Int?" 类型 或者 "optional Int"
因为这个初始化器可能会失败,所以它返回的是一个 可选 的 Int
,而不是一个普通的 Int
。一个可选的 Int
写作 Int?
,而不是 Int
。问号表示了包含的值是可选类型,也就是说可能包含值也可能什么都没有。(不能包含其他任何值,比如 Bool
和 String
类型的值,只能是要么 Int
,要么什么也没有)。
空值
通过指派特殊值 nil
,你可以把一个可选变量设定为空值状态:
var serverResponseCode: Int? = 404
// 变量 serverResponseCode 包含一个整型数值 404
serverResponseCode = nil
// serverResponseCode 为空值
注意
非可选状态下的常量或变量不能使用
nil
。在某些特定条件下,如果你代码中的常量或变量需要指定为空值,则始终将其声明为适当类型的可选值。
如果你定义了一个可选变量但没有赋值,变量将自动设置为 nil
:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
注意
Swift 里的
nil
不同于 Objective-C 里的nil
。 在 Objective-C 里,nil
是一个指向空对象的指针。在 Swift 里,nil
不是指针,而是某种特定类型值的缺失。 任意 类型都可以设置为nil
, 而不仅仅是对象类型。
if 语句和强制解析
你可以在通过一个 if
语句里比较可选项和 nil
的方式来确定其是否包含确定值 。执行比较需要用到“等于”操作符 (==
) ,或者“不等于”操作符(!=
)。
如果一个可选项包含值,那么它就被认为不等于 nil
:
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// 打印 "convertedNumber 包含整型值."
一旦你确认可选项包含值,你就可以通过使用在可选项名称后添加 !
的方式来访问它的值。感叹号的含义是:“我知道这个可选项绝对有值,请使用它。”这就是对可选项值的强制解析。
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 打印 "convertedNumber 包含整型值 123."
更多关于 if
语句, 参考 Control Flow.
注意
尝试使用
!
访问不存在的可选值将触发运行错误。在使用!
做强制解析之前,一定要确保可选项包含一个非nil
值。
可选绑定
使用可选绑定 optional binding 来判断一个可选类型是否包含值,如果包含就赋给一个临时的常量或者变量使这个值可用。可选绑定可以被用到 if
和 while
语句中,用来检查一个值是否是可选类型, 并且将值提取为一个常量或者变量。 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")
}
// Prints ""123" has an integer value of 123"
上面的代码可以被理解为:
“如果通过 Int(possibleNumber)
返回的可选 Int
类型包含一个值,那么就创建一个名为 actualNumber
的新的常量并把可选类型中的值赋给它。”
如果转换成功,常量 actualNumber
可以被用在 if
语句的第一个分支中。它已经被可选类型 包含的 值初始化,因此不再需要使用后缀 !
来获取它的值。在这个例子中,actualNumber
被简单地用来打印转换的结果。
常量和变量都可以使用可选项绑定。如果你想要操纵 if
语句中的第一个分支的 actualNumber
的值,你可以使用 if var actualNumber
来代替,并且可选项内部包含的值会被设置成一个变量而不是一个常量。
你可以在同一个 if
语句中包含多可选项绑定和布尔条件,用逗号隔开。如果任一可选绑定结果是 nil
或布尔值为 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 类的初始化过程中。
隐式展开可选项是后台中通用的可选项,但是同样也可以像非可选值来使用,每次访问的时候不需要展开。接下来的例子中展示了在访问被明确为 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!)
}
// 打印 "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()
// 无错误抛出
} 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()
方法将会被调用。如果抛出了错误,并且匹配到了 outOfCleanDishes
条件的话,washDishes()
函数就会被调用。如果匹配到了 SandwichError.missingIngredients
条件,buyGroceries(_:)
函数就会被调用,并且使用 catch
捕获的关联 String
值作为参数。
抛出,捕获以及传播错误的详细内容请参考 错误处理。
断言与先决条件
断言 和 先决条件 是程序运行时发生的检查动作。你可以使用它们来检查代码被执行之前的一些必要条件是否被满足。如果断言或先决条件中布尔值的条件等于 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.")
// 因为 -3 小于 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
的时候,就使用先决条件。但代码必须 肯定 为 true
才能继续执行下去。例如,使用先决条件去检查下标是否越界或检查函数是否传入了合法的参数值。
通过调用 precondition(_:_:file:line:)
函数来声明一个先决条件。你可以向一个先决条件传入结果为 true
或 false
的表达式和当结果为 false
时的提示信息。例如:
// 判断下标...
precondition(index > 0, "Index must be greater than zero.")
你也可以使用 preconditionFailure(_:file:line:)
函数来表明执行的失败 --- 例如,如果一个 switch
语句的默认条件命中了,但是所有有效的输入数据只会被其他条件处理。
注意
如果你以不检查的编译模式(
Ounchecked
)模式进行编译,先决条件将不会起作用。编译器会假设先决条件总是为真,并会根据你的代码做相应的优化。然而,无论优化设置如何,fatalError(_:file:line:)
函数总会停止程序的执行。你可以在原型设计和开发的早期过程中使用
fatalError(_:file:line:)
函数来创建尚未实现的功能的存根,编写fatalError("Unimplemented")
作为存根的实现。因为fatal error
永远不会被优化,与断言和先决条件不同的是,你可以确保程序总是在遇到存根实现时停止。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: