虽说 Swift 是开发 iOS 及 OS X 应用的一门新编程语言,但它的开发体验与 C 或 Objective-C 有很多相似之处。

Swift 重新实现了 C 与 Objective-C 中的所有基础类型,包括表示整数的 Int,表示浮点数的 DoubleFloat,表示布尔值的 Bool,以及表示纯文本数据的 String。 Swift 还为两个基本集合类型 ArrayDictionary 提供了强大的支持,详情可参考 集合类型

与 C 语言类似,Swift 也采用变量存储数据,并通过标识符来引用变量值。 Swift 还扩充了值不可变的量——常量的用法,使它比 C 语言中的常量强大得多。 当在 Swift 中操作不需要改变值的数据时,使用常量可使代码更安全、更简洁。

除常见类型以外,Swift 还引入了 Objective-C 中不存在的高级类型,其中包括元组(tuple),可以新建或传递一组值。函数可以将多个值作为整体(单个元组值)返回给调用方。

Swift 还引入了可选量,可处理不存在的值。可选量可以“存在一个值 x”,也可以“不存在任何值”。可选量与 Objective-C 为指针赋 nil 相似,但在 Swift 中可以对任意类型使用,而不只针对类。可选量比 Objective-C 的 nil 指针更安全且语义更丰富,在 Swift 最强大的诸多功能中得到了深入的应用。

可选量是 Swift 类型安全的一点体现。Swift 可帮助你清晰地了解代码能处理的数据类型。如果代码希望得到 String 数据,类型安全的特性将阻止你偶然将 Int 传递过去。这样可以在开发过程中尽可能早地发现与修正问题。

常量与变量

常量及变量将名称(如maximumNumberOfLoginAttemptswelcomeMessage) 与特定类型的值(如数字 10 或字符串 "Hello")关联起来。常量一旦赋值,其值不可再改变;而变量以后还可以改赋不同的值。

常量及变量的声明

常量与变量在使用之前必须声明。使用 let 关键词声明常量,使用 var 关键词声明变量。 下面是可以跟踪用户登录次数的常量与变量的例子:

  1. let maximumNumberOfLoginAttempts = 10 // 允许尝试登录的次数
  2. var currentLoginAttempt = 0           // 已经尝试登录的次数

这段代码可以这样理解:

“声明一个新的常量,其名称为 maximumNumberOfLoginAttempts,并将其赋值为 10。 然后,声明一个新的变量,其名称为 currentLoginAttempt,并赋初始值为 0。”

在本例中,允许尝试的最多登录次数作为常量声明,因为允许的次数在执行时永远不会发生变化。当前已尝试次数的计数器作为变量声明,因为该值必须在登录失败时递增。

可以在同一行声明多个常量或变量,以逗号分隔:

  1. var x = 0.0, y = 0.0, z = 0.0

提示:如果代码中需要存储的值不会改变,务必通过 let 关键字作为常量声明。只有存储需要改变的值时才需要使用变量。

类型说明

声明常量或变量时可提供类型说明,明确指定该常量或变量所能存储的数据类型。类型说明的写法为,在常量或变量名称后加上一个冒号、一个空格,后接要使用的类型名称。

本例为名为 welcomeMessage 的变量提供类型说明,指明该变量可存储 String 型的值:

  1. var welcomeMessage: String

声明语句中的冒号意为“…的类型为…”,因此上面的代码可以这样理解:

“声明一个名为 welcomeMessage 的变量,其类型为 String。”

其中“类型为 String”代表“可存储任意 String 类型的值”。可以理解为可以存放的“东西的类型”(或“东西的种类”)。

welcomeMessage 变量现在可以赋任意字符串值,不会报错:

  1. welcomeMessage = "Hello"

提示:在实践中需要编写类型说明的情况非常罕见。如果你在定义常量或变量时提供了初始值,Swift 通常能够推断出该常量或变量应使用的类型,详情参见 类型安全及类型推断。在上述 welcomeMessage 例子中,没有提供初始值,因此才通过类型说明给 welcomeMessage 变量明确指定类型,而没有让它通过初始值推断。

常量与变量的命名

常量及变量的名称可以使用几乎所有字符,包括 Unicode 字符:

  1. let π = 3.14159
  2. let 你好 = "你好世界"
  3. let 🐶🐮 = "汪哞"

常量及变量的名称不可以包含数学符号、箭头、私有(即无效的)Unicode 码位 或绘制线条/方框用的字符。并且名称不能以数字开头, 但除了开头的其他地方都可以使用数字。

常量及变量一旦声明为一个特定类型,则不能以同样的名称再次声明,也不能更改使其存放不同类型的值。同样,也不允许将常量再次声明为变量,或将变量再次声明为常量。

提示:如果需要将常量或变量命名为 Swift 的保留字,可以在将该关键字作为名称使用时用反引号(`)包围。尽管如此,你还是应该避免将关键字作为名称使用,除非迫不得已。

可以将现有变量改赋兼容类型的值。在下例中,friendlyWelcome 被从 "Hello!" 改为 "Bonjour!"

  1. var friendlyWelcome = "Hello!"
  2. friendlyWelcome = "Bonjour!"
  3. // friendlyWelcome 现在为 "Bonjour!"

与变量不同,常量的值一旦确定则不可更改。尝试更改则会报编译错误:

  1. let languageName = "Swift"
  2. languageName = "Swift++"
  3. // 编译时错误 - languageName 不可更改

输出常量及变量

可以通过 println 函数输出常量或变量的当前值:

  1. println(friendlyWelcome)
  2. // 输出 "Bonjour!"

println 是一个全局函数,可向适当的输出界面输出值,末尾接换行符。例如在 Xcode 环境开发,println 会将输出内容输出至 Xcode 的“控制台”面板。(另一个函数 print 执行几乎一样的操作,不同之处在于,后者不会在待输出值的末尾添加换行符。)

println 函数可输出传给它的任何 String 值:

  1. println("这是一个字符串")
  2. // 输出 "这是一个字符串"

println 还能输出更复杂的日志消息,用法与 Cocoa 的 NSLog 函数相似译注:即 C 语言的 printf() 风格。消息内容可以包括常量与变量的当前值。

Swift 通过字符串内插string interpolation)将常量或变量的名称作为占位符内嵌到较长的字符串中,借此提示 Swift 将其替换为常量或变量的当前值。将名称置于括号之间,并在左括号之前通过反斜杠转义:

  1. println("friendlyWelcome 当前的值为 \(friendlyWelcome)")
  2. // 输出 "friendlyWelcome 当前的值为 Bonjour!"

注:字符串内插可用的所有选项参见 字符串内插 一节。

注释

通过注释,可在代码中嵌入不可执行的文本,作为笔记或对自己的提示。在编译代码时,Swift 编译器会忽略注释内容。

Swift 中的注释与 C 语言非常相似。单行注释以两个左斜杠开头(//):

  1. // 这是一条注释

还可以编写多行注释,以左斜杠加星号(/*)开头,并以星号加左斜杠(*/)结尾:

  1. /* 这也是一条注释,
  2. 但跨越多行 */

与 C 语言的多行注释有所不同的是,Swift 的多行注释可以嵌套在其他多行注释内部。写法是在一个多行注释块内插入另一个多行注释。第二个注释块封闭时,后面仍然接着第一个注释块:

  1. /* 这是第一个多行注释的开头
  2. /* 这是嵌套的第二个多行注释 */
  3. 这是第一个多行注释的结尾 */

嵌套式多行注释允许你迅速简便地注释掉大段代码,而不用担心这段代码中是否已经存在多行注释。

分号

与其他很多语言不同,Swift 不要求在每条语句末尾加上分号(;),但只要你愿意,加上也无妨。不过,如果要在同一行书写多条语句,分号是必需的:

  1. let = "🐱"; println()
  2. // 输出 "🐱"

    整数

整数(integer)指没有小数部分的整数,如 42-23。整数既可以是有符号的signed,正数、零、负数)也可以是无符号的unsigned,正数或零)。

Swift 提供 8、16、32、64 位形式的有符号及无符号整数。这些整数类型遵循 C 语言的命名规约,如 8 位无符号整数的类型为 UInt8,32 位有符号整数的类型为 Int32。与 Swift 中的所有类型一样,这些整数类型的名称以大写字母开头。

整数的边界

各整数类型允许的最小值及最大值可通过 minmax 属性获得:

  1. let 最小值 = UInt8.min // 最小值 等于 0,类型为 UInt8
  2. let 最大值 = UInt8.max // 最大值 等于 255,类型为 UInt8

这些属性的值的类型与对应宽度的数据类型一致(如上例为 UInt8),因此也可以在表达式中与同类型的其他数值一起使用。

Int

绝大多数情况下,你并不需要自己决定代码中要使用的整数宽度。Swift 还提供了一个整数类型 Int,其宽度与当前平台的原生字长(word size)一致:

  • 在 32 位平台,IntInt32 宽度一致。
  • 在 64 位平台,IntInt64 宽度一致。

除非你需要处理特定宽度的整数,在代码中应该只使用 Int 表示整数。这样可以保证一致性及互运算性。即使是在 32 位平台,Int 也能存储 -2,147,483,6482,147,483,647 的任意数值,对于很多整数区间需求来说已经足够大了。译注:信苹果会丢饭碗的

UInt

Swift 还提供了无符号整数类型 UInt,其宽度与当前平台的原生字长一致:

  • 在 32 位平台,UIntUInt32 宽度一致。
  • 在 64 位平台,UIntUInt64 宽度一致。

注:只有在特别需要宽度与平台原生字长一致的时才需要使用无整数类型 UInt。否则应使用 Int,即使要存储的值一定非负。总使用 Int 表示整数值有助于保证代码互运算性、避免不同数据类型的转换,并且与整数类型推断相匹配,参见 类型安全及类型推断

浮点数

浮点数 表示有小数部分的数字,例如 3.141590.1-273.15

浮点数类型可以表示的值比整数类型宽广得多,也能存储 Int 类型能存放的最大及最小值。Swift 提供两种有符号的浮点数类型:

  • Double 表示一个 64 位浮点数。在浮点数值非常大或非常精确时使用它。
  • Float 表示一个 32 位浮点数。在浮点数值不需要 64 位精度时使用它。

Double 的精度为 15 个十进制有效数字,而 Float 的精度只有 6 位十进制有效数字。应根据代码所需数值的特点及值域选用合适的浮点数类型。

类型安全及类型推断

Swift 是一门类型安全的语言。类型安全要求代码中值的类型非常明确。如果代码中要求提供 String 数据,则不会错误地向它传递 Int 数据。

由于 Swift 类型安全,它会在编译代码时执行类型检查,并将任何类型不匹配的地方标为错误。这样可以在开发过程中尽可能早地发现并修复问题。

类型检查有助于在操作不同类型值时避免错误。然而,这并不意味着你必须为声明的每个常量与变量指定类型。如果你不指定所需值的类型,Swift 会通过类型推断type inference)求得适当的类型。类型推断允许编译器在编译代码时,根据你提供的值,自动推测出特定表达式的类型。

得益于类型推断,Swift 对类型声明的需要比起 C 或 Objective-C 语言而言要少很多。常量与变量仍然有明确的类型,但明确指定类型的工作已经由编译器代你完成。

类型推断在你声明常量或变量的同时提供初始值时尤其有用。通常通过在声明时赋字面值literal value,或称“字面量literal)实现。(字面值指直接出现在源代码中的值,如下例中的 423.14159。)

例如,如果将字面值 42 赋给新的常量,而不明确讲它是什么类型,Swift 会推断出你希望该常量为 Int 类型,因为你初始化时提供的数字像是个整数:

  1. let meaningOfLife = 42
  2. // meaningOfLife 被推断属于 Int 类型

类似地,如果不为浮点数字面量指定类型,Swift 会推断出你希望创建一个 Double 变量:

  1. let pi = 3.14159
  2. // pi 被推断属于 Double 类型

Swift 在推断浮点数类型时总会选用 Double(而不用 Float)。

如果在表达式中同时使用整数与浮点数字面量,将根据上下文推断得到 Double 类型:

  1. let anotherPi = 3 + 0.14159
  2. // anotherPi 也被推断为 Double 类型

字面值 3 没有明确的类型,自身也不属于某个明确的类型,但由于加法中出现了浮点数字面量,因此推断出合适的输出类型为 Double

数字字面量

整数字面量可以以下面的形式书写:

  • 十进制数,无需前缀
  • 二进制数,以 0b 为前缀
  • 八进制数,以 0o 为前缀
  • 十六进制数,以 0x 为前缀

下述整数字面量的值均为十进制的 17

  1. let 十进制整数 = 17
  2. let 二进制整数 = 0b10001 // 17 的二进制表示
  3. let 八进制整数 = 0o21 // 17 的八进制表示
  4. let 十六进制整数 = 0x11 // 17 的十六进制表示

浮点数字面值可以为十进制(无需前缀),也可以是十六进制(以 0x 为前缀)。小数点两侧均必须有数字(或十六进制数字)。还可以有一个可选的幂次exponent),对十进制浮点数为大写或小写的 e,对十六进制浮点数为大写或小写的 p

对幂次为 exp 的十进制数,基数将乘以 10exp

  • 1.25e2 即 1.25 × 102125.0
  • 1.25e-2 即 1.25 × 10-20.0125

对幂次为 exp 的十六进制数,基数将乘以 2exp

  • 0xFp2 即 15 × 2260.0
  • 0xFp-2 即 15 × 2-23.75

下述所有浮点数字面量的值均为十进制的 12.1875

  1. let 十进制双精度浮点数 = 12.1875
  2. let 幂次表示的双精度浮点数 = 1.21875e1
  3. let 十六进制双精度浮点数 = 0xC.3p0

数字字面量可以包含额外的格式以便于阅读。整数与浮点数均可以添加多余的零或下划线以提高可读性。两种格式均不会影响字面量的实际值:

  1. let 经填充的双精度浮点数 = 000123.456
  2. let 一百万 = 1_000_000
  3. let 一百万多一点点 = 1_000_000.000_000_1

    数字类型转换

Int 类型应作为所有常规用途的整数常量及变量的类型,即使它们确实非负。通常情况下,使用默认的整数类型意味着这些整型常量与变量均可即时互相参与运算,并可与根据整数字面值推断出的类型相匹配。

仅当手中的任务必须使用其他整数类型时才用它们,如外部数据源提供宽度明确的数据,或为了性能、内存占用等其他必需优化考虑。在这些情况下使用宽度明确的类型有助于发现偶然的数值溢出,并还原这些数据实际使用时的特点。

整数转换

不同类型的整数常量或变量所能存储的值域不同。Int8 常量或变量能存储 -128127,而 UInt8 常量或变量能存储 0255。无法存放进某常量或变量的数字会报编译时错误:

  1. let cannotBeNegative: UInt8 = -1
  2. // UInt8 不能存储负数,因此会报错
  3. let tooBig: Int8 = Int8.max + 1
  4. // Int8 不能存储大于其最大值的数字,
  5. // 因此这里也会报错

由于不同数据类型能存储的值域不同,在进行数据转换时需要具体问题具体对待。这种实际选择的过程可避免隐式转换的问题,并使类型转换的意图在代码中明确地展现出来。

要将一种数据类型转换为另一种,应使用现有值初始化一个所需类型的新数。下例中,常量 twoThousand 的类型为 UInt16,而常量 one 的类型为 UInt8。它们无法直接相加,因为类型不同。因此,本例将调用 UInt16(one) 新建一个 UInt16 数,并以 one 的数值初始化,并将新值放在调用处:

  1. let twoThousand: UInt16 = 2_000
  2. let one: UInt8 = 1
  3. let twoThousandAndOne = twoThousand + UInt16(one)

现在加号两侧均为 UInt16 类型,因此允许相加。输出的常量 (twoThousandAndOne) 推断得出的类型为 UInt16,因为它是两个 UInt16 值的和。

某类型(赋初始值) 是调用 Swift 类型构造函数并传递初始值的默认方法。幕后运作情况是,UInt16 有一个接受 UInt8 值的构造函数,因此该构造函数会被用于根据现有 UInt8 创建新的 UInt16。不过,在这里并不能传入任意类型——只能传入 UInt16 提供有构造函数的类型。扩展现有类型使其提供接受新类型(包括自己定义的类型)的构造函数的方法请见 扩展 一章。

整数与浮点数转换

整数与浮点数类型之间的转换必须显式指定:

  1. let three = 3
  2. let pointOneFourOneFiveNine = 0.14159
  3. let pi = Double(three) + pointOneFourOneFiveNine
  4. // pi 等于 3.14159,推断得到的类型为 Double

这段代码中,常量 three 被用来创建新的 Double 类型值,这样加法两侧的类型才一致。不进行类型转换的话,两侧将不允许相加。

浮点数到整数的逆向转换同样可行,整数类型可以用 DoubleFloat 值初始化:

  1. let integerPi = Int(pi)
  2. // integerPi 等于 3,推断得到的类型为 Int

这样用浮点数初始化新整数时,浮点数值总会被截断。即, 4.75 变为 4-3.9 变为 -3

多个数字常量或变量的结合规则与数字字面量的结合规则不同。字面量 3 可以直接与字面值 0.14159 相加,因为数字字面量没有明确指定类型,它们自身也没有明确的类型。其类型仅当被编译器求值时才推断得出。

类型别名

类型别名type aliases)为现有类型定义可替代的名称。类型别名通过 typealias 关键字定义。

类型别名在需要以上下文中更为合适的名称称呼某个现有类型时非常有用,例如当处理来自外部数据源的特定宽度的数据时:

  1. typealias 音频采样 = UInt16

类型别名定义完成后,即可在可能用到原名的地方使用别名:

  1. var 已发现的最大振幅 = 音频采样.min
  2. // 已发现的最大振幅 现在为 0

此处 音频采样 作为 UInt16 的别名定义。因为它是别名,因此对 音频采样.min 的调用实际上会调用 UInt16.min,最终为 已发现的最大振幅 变量提供初始值 0

布尔值

Swift 实现了基本的布尔boolean)类型,称为 Bool。布尔值也称为逻辑值logical),因为只能为true)或false)。Swift 提供了两种布尔常量值:truefalse

  1. let 橘子是橘子 = true
  2. let 芜菁很好吃 = false

橘子是橘子芜菁很好吃 的类型被推断为 Bool,因为它们以布尔字面值初始化。与上文中的 IntDouble 一样,并不需要明确声明为 Bool,只要在创建变量的同时用 truefalse 初始化。以已知类型的值初始化常量或变量时,类型推断使 Swift 的代码更简练、更具可读性。

控制条件语句(如 if 语句)时,布尔值尤其有用:

  1. if 芜菁很好吃 {
  2.     println("唔,芜菁真香!")
  3. } else {
  4.     println("呸,恶心死了。")
  5. }
  6. // 输出 "呸,恶心死了。"

if 等条件语句的详细情况请见 流程控制

Swift 的类型安全特性可避免非布尔值被当作 Bool 使用。下面的例子会报编译时错误:

  1. let i = 1
  2. if i {
  3.     // 本例无法通过编译,报编译错误
  4. }

换成下例便可以通过:

  1. let i = 1
  2. if i == 1 {
  3.     // 本例可成功编译
  4. }

i == 1 比较的结果类型为 Bool,因此第二个例子可以通过类型检查。i == 1 这类的比较在 基本运算符 一章讨论。

与 Swift 中的其他类型检查规则一样,这些规则可避免偶然错误,并确保各段代码的目的总是明确的。

元组

元组将多个值组合为单个值。元组内的值可以是任意类型,各元素不必是相同的类型。

在本例中,(404, "Not Found") 是描述一条 HTTP 状态码HTTP status code)的元组。HTTP 状态码 是请求任何网页时,web 服务器返回的特殊值。如果请求了不存在的网页,则会返回状态码 404 Not Found

  1. let http404错误 = (404, "Not Found")
  2. // http404错误 的类型为 (Int, String),其值为 (404, "Not Found")

(404, "Not Found") 元组将一个 Int 值与一个 String 值组合起来,表示 HTTP 状态码的两个值:一个数字和一个供人类阅读的描述。它可以这样描述:“类型为 (Int, String) 的元组”。

你可以将类型任意排列来创建元组,它们可以包含任意多种不同的类型。只要你愿意,创建类型为 (Int, Int, Int)(String, Bool) 的元组也不会有问题,只要这种排列对你有意义。

元组的内容可以还原decompose)为独立的常量或变量,然后便可照常访问:

  1. let (状态码, 状态消息) = http404错误
  2. println("状态码为 \(状态码)")
  3. // 输出 "状态码为 404"
  4. println("状态消息为 \(状态消息)")
  5. // 输出 "状态消息为 Not Found"

如果你只需要元组的一部分值,可以在还原元组时用下划线(_)忽略掉其他部分:

  1. let (只留状态码, _) = http404错误
  2. println("状态码为 \(只留状态码)")
  3. // 输出 "状态码为 404"

还可以通过以 0 开头的索引号访问元组的各个元素值:

  1. println("状态码为 \(http404错误.0)")
  2. // 输出 "状态码为 404"
  3. println("状态消息为 \(http404错误.1)")
  4. // 输出 "状态消息为 Not Found"

还可以在定义元组时为各元素命名:

  1. let http200状态 = (状态码: 200, 描述文字: "OK")

为元组各元素命名后,便可以通过元素名称访问各元素的值了:

  1. println("状态码为 \(http200状态.状态码)")
  2. // 输出 "状态码为 200"
  3. println("状态消息为 \(http200状态.描述文字)")
  4. // 输出 "状态消息为 OK"

元组在作为函数返回值时尤其有用。一个获取网页内容的函数可能会返回 (Int, String) 元组类型,来描述网页装取是成功还是失败。函数返回两个类型完全不同的值描述结果,所能提供的信息比只能返回固定类型的单个值要有用得多。详情请参见 返回多个返回值的函数

元组对临时组合相关的多个值非常有用。它们并不适合用来创建复杂的数据结构。如果你的数据结构的生命期超过临时使用的范畴,请将它作为类或结构建模,而不是以元组存储。详情请见 类与结构

可选量

在值可能不存在时使用可选量optional)。可选量是指:

  • 存在一个值,这个值等于 x

或者

  • 不存在任何值

注:可选量的概念在 C 和 Objective-C 中并不存在。Objective-C 中最相近的是,一个以对象为返回值的方法,可以返回 nil,表示“不存在有效的对象”。不过,该规则只对对象有效——对于结构体、基本的 C 类型或枚举值均不起作用。对于这些类型,Objective-C 语言的方法通常会返回一个特殊值(如 NSNotFound)来表示值不存在。这种策略假定该方法的调用方知道要测试返回值是否等于某个特殊值,并且记得要作此检查。Swift 的可选量允许表示任何类型不存在值,无需定义特殊常量。

举例说明。Swift 的 String 类型有一个名为 toInt 的方法,可尝试将 String 值转为 Int 值。然而,不是所有字符串都可以转换为整数。字符串 "123" 可以转换为数值 123,而字符串 "hello, world" 却显然没有对应的数值。

下面的例子会利用 toInt 方法,尝试将 String 转换为 Int

  1. let 可能是数字 = "123"
  2. let 转换得到的数字 = 可能是数字.toInt()
  3. // 转换得到的数字 被推断为 "Int?" 类型,即 "可选的 Int"

由于 toInt 方法可能转换失败,因此它会返回一个 可选的 Int 型,而不是 Int 型。可选的 Int 记作 Int?,而不是 Int。其中的问号表示该类型包含的值是可选的,即 Int? 可能包含某个 Int 类型的值,也可能不含任何值。(但不能包含其他类型的值,如 Bool 值或 String 值。不是 Int 就是不存在。)

if 语句与强制拆包

可以使用 if 语句测试可选量是否包含值。如果存在,则求值结果为 true;否则为 false

一旦确认可选量的确包含值,便可以通过在变量名末尾添加感叹号(!)访问其内部的值。感叹号明确表达:“我知道这个可选量的确存在值;请使用那个值。”这种操作称为对可选值进行强制拆包force-unwrap):

  1. if 转换得到的数字 {
  2.     println("\(可能是数字) 的整数值为 \(转换得到的数字!)")
  3. } else {
  4.     println("\(可能是数字) 无法转换为整数")
  5. }
  6. // 输出 "123 的整数值为 123"

关于 if 语句的更多信息,请见 流程控制

注:尝试用 ! 访问不存在的可选值时会导致运行时错误。在用 ! 强制拆包之前,务必确保可选量的确包含非 nil 的值。

可选值绑定

可以通过可选值绑定optional binding)测试可选量是否包含一个值,如果存在,则将该值以临时常量或变量的形式拆包使用。可选值绑定可以与 ifwhile 语句结合使用,这样只需要一步就可以检查是否存在值、提取该值、并存放到常量或变量中。关于 ifwhile 语句的更多情况在 流程控制 一章中讲解。

if 语句为例,可选值绑定可以这样书写:

  1. if let 常量名称 = 某可选值 {
  2.     一系列语句
  3. }

上文中 可能是数字 一例,可以改写用可选值绑定代替强制拆包:

  1. if let 实际值 = 可能是数字.toInt() {
  2.     println("\(可能是数字) 的整数值为 \(实际值)")
  3. } else {
  4.     println("\(可能是数字) 无法转换为整数")
  5. }
  6. // 输出 "123 的整数值为 123"

可以这样理解:

“如果 可能是数字.toInt 返回的 可选的 Int 包含一个值,则新建一个名为 实际值 的常量,并将其值设为可选量中包含的值。”

如果转换成功,常量 实际值 将可供 if 语句的第一段分支使用。该常量已经以可选量内部的值初始化,因此不再需要用后缀 ! 访问其值。本例中,实际值 被直接用来输出转换结果。

常量与变量均可用于可选值绑定。如果需要在第一个分支中修改 实际值 的值,可以改写为 if var 实际值,这样可选量的值将作为变量而非常量拆包。

nil

要将可选变量设为值不存在的状态,可以给它赋特殊值 nil

  1. var 服务器响应码: Int? = 404
  2. // 服务器响应码 包含一个实际存在的 Int 值:404
  3. 服务器响应码 = nil
  4. // 服务器响应码 现在不含任何值

注:nil 不能用于非可选量。如果代码中的常量或变量需要适配值不存在的特殊情况,务必将它声明为恰当的可选类型。

如果定义的可选量时不提供默认值,该常量或变量将自动设为 nil

  1. var 问卷回答: String?
  2. // 问卷回答 被自动设为 nil

注:Swift 的 nil 与 Objective-C 的 nil 不同。Objective-C 的 nil 是指向不存在对象的指针。而 Swift 的 nil 不是指针——它代表特定类型的值不存在。任何类型的可选量都能赋值为 nil,而不仅限于对象类型。

可选量的隐式拆包

如上所述,可选量指允许“值不存在”的常量或变量。可选量可以通过 if 语句测试是否存在值,也可以通过可选值绑定按条件拆包,并在值存在的情况下才访问可选量的值。

有时根据程序结构可以推断,可选量在首次赋值后,必然存在值。这些情况下,可以不必每次访问时都检测并提取可选量的值,因为可以安全地认为那时一定存在值。

这些可选量可定义为隐式拆包的可选量(implicitly unwrapped optional)。隐式拆包的可选量的声明格式为,在希望标为可选的类型名称后面,用感叹号 (String!) 代替问号 (String?)。

隐式拆包的可选量在可选量首次定义后即确认存在值,在此之后任何时刻都肯定存在的时候有用。Swift 中主要应用在类初始化,详见 外部引用与隐式拆包的可选属性

隐式拆包的可选量在实现级别就是普通的可选量,但能够像非可选量那样使用,无需在每次访问时显式拆包。下例显示了 可选的 String隐式拆包的可选 String 之间的行为差异:

  1. let 可能是字符串: String? = "可选的 String。"
  2. println(可能是字符串!) // 访问其值时需要添加感叹号
  3. // 输出 "可选的 String。"
  4. let 假定是字符串: String! = "隐式拆包的可选 String。"
  5. println(假定是字符串) // 访问其值时无需感叹号
  6. // 输出 "隐式拆包的可选 String。"

可以认为,隐式拆包的可选量即授予可选量在被使用时自动拆包的权限。不必每次使用可选量时都在名称后面添加感叹号,只需在定义时在类型后面加上感叹号即可。

注:如果在隐式拆包的可选量存在值之前就尝试访问,会触发运行时错误。结果与在普通可选量尚未赋值时直接加感叹号引用相同。

隐式拆包的可选量也可以当作普通可选量对待,检查是否存在值:

  1. if 假定是字符串 {
  2.     println(假定是字符串)
  3. }
  4. // 输出 "隐式拆包的可选 String。"

隐式拆包的可选量同样可以结合可选值绑定使用,单条语句完成检查值并拆包的工作:

  1. if let 肯定是字符串 = 假定是字符串 {
  2.     println(肯定是字符串)
  3. }
  4. // 输出 "隐式拆包的可选 String。"

注:当变量在后续过程中可能变为 nil 时,不应使用隐式拆包的可选量。如果在变量声明周期内都需要检查 nil 值,请务必使用普通的可选类型量。

断言

可选量允许检查值的存在与否,并允许代码能够适配不存在值的情况。但也有时候,如果值不存在,或不满足特定条件,代码便不可能继续执行下去。对于这些情况,需要在代码中触发断言assertion译注:“触发”指断言不成立来终止执行,并为找出值不存在或无效的原因创造机会。

借助断言辅助调试

断言是一种运行时检查,确认一定为 true 的逻辑条件是否成立。即,断言“宣告”某条件一定成立。使用断言,可确保在进一步执行后续代码之前,确保必要条件确实已满足。如果条件的求值结果为 true,代码将照常继续运行;如果条件的求值结果为 false,则代码不再继续执行,应用程序随之终止。

如果是在调试环境运行时触发断言(如在 Xcode 中构建并执行应用程序),你将确切地知道异常情况出现的位置,并能在程序执行到该断言位置的状态下调试程序。断言还允许提供一段合适的调试消息,对该断言加以说明。

断言可通过调用全局函数 assert 来实现。将需要求值的表达式(结果为 truefalse)传递给 assert 函数,如果条件求值结果为 false,则应显示错误消息:

  1. let age = -3
  2. assert(age >= 0, "人的年龄不可能小于零")
  3. // 断言触发错误,因为 age >= 0 不成立

本例中,代码仅在 age >= 0 的求值结果为 true 时才继续执行,即,age 的值为非负数才继续。如果 age 的值为负数,即上述代码中的情况,则 age >= 0 的求值结果为 false,于是断言触发,应用程序终止。

断言的消息内容不能使用字符串内插。如果需要,可省略断言消息,如下例所示:

  1. assert(age >= 0)

何时应使用断言

仅当条件可能为假、但必须一定为真代码才能继续执行时,才应使用断言。适合运用断言检查的场景包括:

  • 向自定义下标实现传递了整数下标索引,但该索引号可能太小或太大。
  • 值传递给了函数,但如果值无效,函数无法完成其任务。
  • 可选值当前为 nil,但后续代码要求值非 nil 方可成功执行。

参见 下标函数

注:断言可使应用程序终止。断言不适合设计不太可能出现无效条件的场景。尽管如此,在可能出现无效条件的情况下,断言仍不失为在开发过程中确保这些问题在发布以前得到关注与处理的有效途径。