Swift语言指南(一)基础知识

Swift 是用于开发iOS和OS X的一门新编程语言。尽管如此,Swift的大部分地方看着其实跟C和Objective-C都是挺像的。

Swift 提供了C和Objective-C基础类型的自己一套版本,包括用于整型的Int,用于浮点值的Double和Float,用于布尔值的Boolean,和用于文本数据的String。Swift还提供了Array和Dictionary这两种集合类型的增强版本。

像 C 一样, Swift 通过使用变量来存储和引用值。 Swift 还支持值不能变的变量。就是我们所熟悉的常量,不过比C里面的常量强多了。常量用于整个Swift中,让你在处理不需要改变值的时候使得代码更安全和简洁。

除了熟悉的类型, Swift 还加入了Objective-C里没有的类型,比如元组。元组类型可以创建并传递成组的值。通过元组,你可以从函数以一个复合值的形式返回多个值。

Swift 还加入了可选项,用以处理无值的情况。可选项要么是“有个值,等于 x“,要么是“啥值都没有“。可选类型跟 Objective-C 里指针用 nil 类似,不过它们可以用于任何类型,并不是只能用于类。可选类型比 Objective-C 里的 nil 指针更安全,更具表达性,它也是Swift众多强大特性里最核心的一个。

可选项乃 Swift 是类型安全语言的真实案例。Swift 使你明了你的代码可以运行什么类型的值。如果你代码想要一个String,类型安全可以防止你不小心传个 Int 过去。这个限制让你在开发阶段时尽早捕获并修复异常。

常量与变量

常量和变量将一个名称(比如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

定义里的冒号意思是 “…是属于类…,”,因此上面的代码可以读作:

“定义一个String类型的名为 welcomeMessage 的变量。”

“String 类型的” 这句话意思是“可以存储任何 String 值。” 把它想成这个意思:可以存储的“东西的类型“(或者“东西的种类“)。

welcomeMessage 变量现在可以不出任何异常地设置成任何字符串值了:

welcomeMessage = "Hello"

你可以在同一行代码里定义多个相关的同类型变量,以逗号分隔,在最后一个变量名后面加上一个类型标注:

var red, green, blue: Double

注意

在正式写代码的时候写类型标注是很少见的。如果你定义常量或变量的时候赋初始值了的话,Swift 几乎都可以推断出该常量或变量的类型来,就如在类型安全和类型推断里讲到的那样。在上面 welcomeMessage 的例子里,没有给初始值,因此 welcomeMessage 变量的类型由类型标注来进行指定,而不是从初始值推断。

常量和变量的命名

常量和变量的名称几乎可以包含任何字符,包括Unicode 字符:

这里写图片描述

常量和变量名不能包含空格字符、数学符号、剪头、私用的(或无效的)Unicode 代码点或者画线画盒的字符。也不可以以数字开头,虽然数字可以包含在名称的其它地方。

一旦你定义了某个类型的常量或变量之后,你就不能再用同一个名称再次进行定义,或者用来存储其它类型的值。也不能把常量改为变量或者变量改为常量。

注意

如果你要把常量或变量命名为 Swift 里的关键字的话,用返回标记 (`) 包住关键字。然而,除非迫不得已,否则不要用关键字来命名。

你可以将一个已有的变量的值更改为另一个兼容类型的值。在这个例子里, friendlyWelcome 变量的值从 “Hello!” 改为 “Bonjour!”:

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 现在是 "Bonjour!"

跟变量不同,常量的值设置了之后就不能更改了。尝试这么做的话会在代码编译时得到一个异常:

let languageName = "Swift"
languageName = "Swift++"
// 这是一个编译时异常—— languageName 不能被修改

打印常量和变量

你可以用 println 函数打印常量和变量的当前值:

println(friendlyWelcome)
// 打印出 "Bonjour!"

println 是一个全局函数,用来打印一个值,后面紧跟换行,输出到合适的地方。例如在Xcode里,println 将输出打印在Xcode的“控制台“面板。(另一个函数,print,也是执行一样的任务,只是在打印的值后面不会换行)。

println 函数会打印出任何传入的String值:

println("This is a string")
// 打印出 "This is a string"

println 函数可以打印更复杂的日志信息,跟 Cocoa 的 NSLog 函数很相似。这些信息可以包括常量和变量的当前值。

Swift 使用字符串插值在一个长字符串中把常量或变量名作为占位符包括进来,并提示 Swift 用该常量或变量的当前值把占位符替换之。用括号把名称括起来,再在左括号前加个反斜杠来进行转义:

println("The current value of friendlyWelcome is \(friendlyWelcome)")
// 打印出 "The current value of friendlyWelcome is Bonjour!"

注意

字符串插值那一章将详细介绍该特性的所有选项。

注释

代码里不执行的文本用注释来表示,作为给自己看的备注或提醒。代码编译时注释将会被 Swift 编译器所忽略。

Swift 里的注释和 C 里的注释很相似。单行注释以两个斜杠开头 (//):

// 这是一个注释

多行注释以斜杠加星号开头 (/) 并以星号加斜杠结束 (/):

/* 这也是注释,
不过写在多行了 */

跟 C 里的多行注释不同,Swift 里的多行注释可以嵌套在其它多行注释里。要写嵌套注释,首先以一个多行注释块开头,然后在第一个块里写第二个多行注释的开头。然后第二个块关闭,接着是第一个块关闭:

/* 这是第一个多行注释的开头
/* 这里是第二个,嵌套的多行注释 */
这是第一个多行注释的结束 */

嵌套多行注释可以使得你快捷方便地对大段代码进行注释,即使这段代码已经包含多行注释了。

分号

不像很多其它的语言,Swift 不要求你非得在代码的每一个语句后面写分号 (;) ,当然你原意的话也可以写。然而,如果要在同一行内写多个语句的话,就必须要加分号了:

这里写图片描述

整型

整型全部由数字组成,没有分数,比如42,-23。整型要么有符号(正数,0,或者负数),要么无符号(正数或0)。

Swift 提供了8,16,32和64位形式的有符号和无符号整数。这些整数遵循了和 C 相似的命名约定,8位无符号整数是 UInt8 类型,32位有符号整数是 Int32 类型。就像 Swift 里的所有类型一样,这些整数类型都是大写字母开头命名。

整数边界

你可以用每个整数类型的 min 和 max 属性来访问它的最小和最大值:

let minValue = UInt8.min  // minValue 等于 0, 而且是 UInt8 类型的
let maxValue = UInt8.max  // maxValue 等于 255, 而且是 UInt8 类型的

这些属性的值都是大小适当的数字类型(如上例中的UInt8),因此可以用在与其他相同类型值的表达式中。

Int

大多数情况下,你不用在代码里指定整数的大小。Swift 提供了一个额外的整数类型,Int ,它与当前平台的本地字大小(native word size)一致:

在32位平台上,Int 的大小和 Int32 一致。
在64位平台上,Int 的大小和 Int64 一致。
除非你需要指定整型的大小,不然都在代码里使用Int 来使用整数吧。这样可以帮助代码具有一致性与互操作性。即便在32位平台上,Int 也可以存储介于 -2,147,483,648 和 2,147,483,647 之间的任何值,这对于很多整数范围而言已经足够了。

UInt

Swift 也提供了一个无符号整型类,UInt,它与当前平台的本地字大小一致:

在32位平台上,UInt 的大小与 UInt32 一致。
在64位平台上,UInt 的大小与 UInt64 一致。

注意

只有在需要特别指定与当前平台的本地字大小一致的无符号整数时才用 UInt 。如果不是的话,Int 优先考虑使用,即使知道要存储的值是非负数。整数值用Int的这种一致性用法使代码具有互操作性,避免了在不同数字类型间转换的必要性,而且正如《类型安全》和《类型推断》这两章所说的,还可以匹配整型类型推断。

浮点数

浮点数就是有小数部分的数字,如 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 那样对类型定义要求那么多。常量和变量仍然是显式给定类型了,不过大部分的指定类型工作已经帮你完成了。

类型推断在定义常量和变量赋初始值时尤其有用。这通常是在定义常量或变量的那一刻通过指定一个字面上的值(或文字)就完成了。(字面值就是在源代码里直接显示的值,如下例的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),基数乘以 10exp:

  • 1.25e2 意思是 1.25 × 10^2, 或者 125.0。
  • 1.25e-2 意思是 1.25 × 10^-2, 或者 0.0125。

对于十六进制的指数(exp),基数乘以 2exp:

  • 0xFp2 意思是 15 × 2^2, 或者 60.0.。
  • 0xFp-2 means 15 × 2^-2, 或者 3.75。

以下的浮点字面值都等于十进制的 12.1875:

let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

数字字面值可以调整格式使它更容易读懂。整数和浮点数都可以用0来填充,还可以加下划线来提高可读性。下面的这些字面值格式都不会影响它的值:

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
// UInt8 不允许存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1
// Int8 不能存储超过它最大值的数,所以也会报错

由于每一个数字类型可以存储不同范围的值,所以必须一个一个选择数字类型转换。这种选择方案防止了隐藏的转换错误并且有助于在代码里显式进行类型转换。

要把一个给定的数字类型转换成其它类型,以已有值初始化一个期望类型的新数字。在下例中,常量 twoThousand 是 UInt16 类型的,而常量 one 是 UInt8 类型的。它们不能直接加到一起,因为他们不是同一类型的。相反,这个例子调用UInt16(one)来创建一个新的 UInt16,用one 的值来初始化,并用这个值来代替原来的:

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 提供初始化器的类型。扩展已有类型,提供可以接受新类型(包括你自己定义的类型)的初始化器将在《扩展》一章予以讲述。

整型与浮点型的转换

整数与浮点数类型的转换必须是显式的:

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159, 被推断为 Double 类型

在这里,常量 three 的值用来创建一个 Double 类型的新值,所以加号两边具有相同的类型。如果没有这个转换的话,这个加法是不会被允许的。

浮点数转换为整数也一样必须是显式的。整型可以用一个 Double 或者 Float 值来初始化:

let integerPi = Int(pi)
// integerPi 等于 3, 被推断为 Int 类型

浮点值以这种方式用来初始化新整型值时总是会被截取的。这说明 4.75 变成 4, 而 -3.9 变成 -3.

注意

将数字常量和变量结合的规则与数字字面值的规则不一样。字面值3可以直接加字面值 0.14159,因为数字字面值本身没有显式类型。它们的类型只有在编译器取它们值的时候才会被推断出来。

类型别名

类型别名为已有的类型定义另一个名字。通过 typealias 关键字来定义类型别名。

在你想通过更合适的名称引用已有类型时,类型别名就很有用了,比如从外界使用有特定大小数据时:

typealias AudioSample = UInt16

一旦定义了类型别名,你可以在任何原类名能用的地方使用别名:

var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 现在是 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 {
    println("Mmm, tasty turnips!")
} else {
    println("Eww, turnips are horrible.")
}
// 打印出 "Eww, turnips are horrible."

if 语句之类的条件语句将在《控制流》那一章里详细说明。

Swift 的类型安全防止了非布尔值用于 Bool。以下例子会抛出编译时异常:

let i = 1
if i {
    // 这个例子会编译不通过,并抛出异常
}

但是,下面的例子是有效的:

let i = 1
if i == 1 {
    // 这个例子会编译成功
}

i == 1 比较式的结果是 Bool 类型的,所以这第二个例子类型检测通过了。像 i == 1 这样的比较式将在《基本操作符》一节中进行讲解。

正如Swift 里类型安全的其它例子一样,这个方法避免了意外的异常并保证了代码的清晰。

元组

元组将多个值组合成为一个复合值。元组内的值可以是任意类型的,而且彼此间不需要是同一类型。

在这个例子里, (404, “Not Found”) 是一个描述 HTTP 状态码的元组。HTTP 状态码是请求网页时从web服务器返回的一个特殊值。如果请求一个不存在的网页就会返回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
println("The status code is \(statusCode)")
// 打印出 "The status code is 404"
println("The status message is \(statusMessage)")
// 打印出 "The status message is Not Found"

如果你仅仅需要元组值的一部分,那么在拆解元组的时候使用下划线 (_) 忽略其余的部分:

let (justTheStatusCode, _) = http404Error
println("The status code is \(justTheStatusCode)")
// 打印出 "The status code is 404"

另外,还可以用以0开始的索引数字来访问元组里的独立元素:

println("The status code is \(http404Error.0)")
// 打印出 "The status code is 404"
println("The status message is \(http404Error.1)")
// 打印出 "The status message is Not Found"

你可以在定义元组时为独立元素命名:

let http200Status = (statusCode: 200, description: "OK")

如果在元组内命名元素,你可以用元素名称来访问这些元素的值:

println("The status code is \(http200Status.statusCode)")
// 打印出 "The status code is 200"
println("The status message is \(http200Status.description)")
// 打印出 "The status message is OK"

元组在函数返回值时特别有用。要返回一个网页的函数可能会返回 (Int, String) 元组类来描述页面获取成功与否。通过返回有两个不同值,不同类型的元组,这个函数就可以比仅返回一个类型一个值提供更有用的输出信息。要了解更多,请参见《返回多个值的函数》一节。

注意

元组对于一组有关联关系的临时值来说很有用。但它们不适合创建复杂的数据结构。如果你的数据结构可能会比较长时间的使用,那么把它定义为一个类或者结构体,而不要定义为元组。更多信息,请参考《类和结构体》一章。

可选项

在值可能缺失的情况下要用到可选项。可选项说的是:

  • 有个值,等于x

或者

  • 根本就没值

注意

可选项的概念在 C 或 Objective-C 里是没有的。在 Objective-C 里最类似的东西是从方法返回 nil 否则返回一个对象,nil 表示“有效对象的缺失“。然而,这仅仅适用于对象——不能用于结构体、基础 C 类型或者枚举值。对于这些类型,Objective-C 方法通常会返回一个特殊的值(比如NSNotFound)来指示值的缺失。这种方式是假定方法调用者知道有这么个特殊值并且记得要检查它。Swift 的可选项让你可以指定任意类型的值缺失,且不需要任何特殊常量。

这里是一个可选项如何用来应对值缺失的例子。Swift 的String 类型有个名为 toInt 的方法,它会尝试将一个 String 值转换为 Int 值。然而,并不是所有的字符串都可以转成整数的。字符串 “123” 可以转换成数字的123,但字符串 “hello, world” 可不能明显地转成数字10。

下面的例子使用 toInt() 方法来试图将 String 转换为 Int:

let possibleNumber = "123"
let convertedNumber = possibleNumber.toInt()
// convertedNumber 被推断为 "Int?" 类型, 或者 "可选的 Int"

由于 toInt() 方法有可能会失败,它返回了一个可选的 Int ,而不是一个 Int 。可选的 Int 写作 Int? ,而不是 Int 。问号表明它包含的值是可选的, 意思是它可能包含一些 Int 值,或者根本没值。(它不可能包含其它别的东西,比如 Bool 值或者 String 值。它要么是个 Int, 要么啥也不是。)

nil

将可选变量设置为无值状态是通过指定名为 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 {
    println("convertedNumber contains some integer value.")
}
// 打印出 "convertedNumber contains some integer value."

一旦确定可选项确实包含值,你就可以通过在可选项的名称后增加一个感叹号(!)来访问它的底层值。感叹号实际上是在说“我知道这个可选项一定是有值的;请尽情使用。“ 这就是所说的可选值的强制开启:

if convertedNumber != nil {
    println("convertedNumber has an integer value of \(convertedNumber!).")
}
// 打印出 "convertedNumber has an integer value of 123."

要了解跟多关于 if 语句的内容,请参见《控制流》一章。

注意

尝试使用 ! 来访问不存在的可选值会触发运行时异常。始终确保在用 ! 强制开启一个值前可选项包含一个非 nil 值。

可选绑定

使用可选绑定来找出可选项是否包含一个值,如果是的话,让这个值作为一个临时常量或变量。可选绑定可以用于 if 和 while 语句来检测可选项里的值,并把这个值释放给一个常量或变量,只需一步操作。 if 和 while 语句在《控制流》一章内将有更多细节。

如下用if语句写一个可选绑定:

if let constantName = someOptional {
    statements
}

你可以重写《可选项》那一节里的 possibleNumber 例子,用可选绑定,而不是强制开启:

if let actualNumber = possibleNumber.toInt() {
    println("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
    println("\'\(possibleNumber)\' could not be converted to an integer")
}
// 打印出 "'123' has an integer value of 123"

这段代码可读作:

“如果由 possibleNumber.toInt 返回的可选 Int 包含值的话,将名为 actualNumber 的新常量设置为可选项里包含的那个值。”

如果转换是成功的话,actualNumber 常量在第一个if语句分支里变得可用。它已经由包含在可选项里的值初始化了,所以没必要使用 !后缀来访问它的值。在这个例子里,actualNumber 仅仅是简单地用来打印转换的结果。

常量和变量都可以用可选绑定。如果你想在 if 语句的第一个分支内想操控 actualNumber 的值,你可以用 var actualNumber 来代替,这样可选项里的值就作为变量而不是常量可以拿来用了。

多个可选绑定可以同时出现在一个 if 语句里,以逗号把每一个赋值表达式分隔开。

if let constantName = someOptional, anotherConstantName = someOtherOptional {
    statements
}

隐式开启可选项

正如上面所述,可选项用来指示常量或变量允许拥有“无值“。可选项可以用一个 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 {
    println(assumedString)
}
// 打印出 "An implicitly unwrapped optional string."

你也可以把可选绑定用在隐式开启可选项里,在一行语句里检测和开启它的值:

if let definiteString = assumedString {
    println(definiteString)
}
// 打印出 "An implicitly unwrapped optional string."

注意

当一个变量在以后可能会变成 nil 的时候就不要用隐式开启可选项了。如果需要检测 nil 值,那么在变量的生命周期里都用普通可选项类型。

断言

可选项可以检测值是否存在,并优雅地应对值缺失的情况。然而在某些时候,如果值不存在或者如果一个给定的值不满足特定的条件的话你的代码根本不可能继续执行。在这些情况下,你可以在代码里触发一个断言来结束代码执行,提供机会来查明值缺失或无效的原因。

用断言来调试

断言是一个逻辑条件肯定为真的运行时检测。从字面上来说,一个断言“assert“一个条件为真。使用断言来确保执行接下来的代码之前要满足满一个必要条件。如果条件为true,代码如常继续执行;如果条件为false,代码执行结束,程序终止。

如果你的代码在调试环境里触发了一个断言,比如在Xcode里生成并运行程序,你能够确切地看到无效状态发生在哪里,并在断言被触发的时候查询程序的状态。断言还让你提供一个合适的调试信息。

要写断言,只需调用全局 assert 函数。将判定真假的表达式传入assert 函数,如果条件结果是false,一条信息会显示出来:

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 这个会导致断言出发,因为 age 不是 >= 0

在这个例子里,代码执行只在如果 age >= 0 为true时才会继续,也就是说 age 的值是非负的。如果 age 的值是负数,就像上面的代码那样, age >= 0 为false,断言被触发,终止程序。

断言信息可以忽略掉,如下例:

assert(age >= 0)

什么时候使用断言

当条件有可能会是 false 的任何时候都可以用断言,但是如果要继续执行代码那必须是true。合适的断言检测场景包括:

整数下标索引超出索引范围。
传入函数的值无效。
可选值当前为 nil,但非nil 值对于后续代码成功执行是必不可少的。
更多内容见《下标与函数》。

注意

断言导致程序终止,但不能用来设计代码作为使无效条件无法冒头的替代方法。否则,可能出现无效条件的时候,断言是在开发阶段,程序发布之前确保这些条件强调突出和注意的有效方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值