声明常量和变量
常量和变量必须在使用前声明,用 let
来声明常量,用 var
来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
这两行代码可以被理解为:
“声明一个名字是 maximumNumberOfLoginAttempts
的新常量,并给它一个值 10
。然后,声明一个名字是 currentLoginAttempt
的变量并将它的值初始化为 0
。”
你可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0.0, y = 0.0, z = 0.0
类型标注
当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
这个例子给
welcomeMessage
变量添加了类型标注,表示这个变量可以存储String
类型的值:var welcomeMessage: String
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:
“声明一个类型为
String
,名字为welcomeMessage
的变量。”“类型为
String
”的意思是“可以存储任意String
类型的值。”
welcomeMessage
变量现在可以被设置成任意字符串:welcomeMessage = "Hello"
你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double
输出常量和变量
你可以用
print(_:separator:terminator:)
函数来输出当前常量或变量的值:print(friendlyWelcome) // 输出 "Bonjour!"
Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
print("The current value of friendlyWelcome is \(friendlyWelcome)") // 输出 "The current value of friendlyWelcome is Bonjour!
类型安全和类型推断
例如,如果你给一个新常量赋值
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
类型。整数转换
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
值的和。类型别名
类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用
typealias
关键字来定义类型别名。当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
typealias AudioSample = UInt16
元组
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
下面这个例子中,
(404, "Not Found")
是一个描述 HTTP 状态码(HTTP status code)的元组。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)
或者其他任何你想要的组合的元组。你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:
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)
元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考函数参数与返回值。可选类型
使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示:
- 有值,等于 x
或者
- 没有值
注意:
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"
因为该构造器可能会失败,所以它返回一个可选类型(optional)
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
if 语句以及强制解析
你可以使用
if
语句和nil
比较来判断一个可选值是否包含值。你可以使用“相等”(==
)或“不等”(!=
)来执行比较。如果可选类型有值,它将不等于
nil
:if convertedNumber != nil { print("convertedNumber contains some integer value.") } // 输出 "convertedNumber contains some integer value."
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(
!
)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):if convertedNumber != nil { print("convertedNumber has an integer value of \(convertedNumber!).") } // 输出 "convertedNumber has an integer value of 123."
可选绑定
使用可选绑定(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") } // 输出 "'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
语句的句中(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) } // 输出 "An implicitly unwrapped optional string."
你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:
if let definiteString = assumedString { print(definiteString) } // 输出 "An implicitly unwrapped optional string."
注意:
如果一个变量之后可能变成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]
作为参数。抛出,捕捉,以及传播错误会在错误处理章节详细说明。
断言
可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个 断言(assertion) 来结束代码运行并通过调试来找到值缺失的原因。
使用断言进行调试
断言会在运行时判断一个逻辑条件是否为
true
。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为true
,代码运行会继续进行;如果条件判断为false
,代码执行结束,你的应用被终止。如果你的代码在调试环境下触发了一个断言,比如你在 Xcode 中构建并运行一个应用,你可以清楚地看到不合法的状态发生在哪里并检查断言被触发时你的应用的状态。此外,断言允许你附加一条调试信息。
你可以使用全局
assert(_:_:file:line:)
函数来写一个断言。向这个函数传入一个结果为true
或者false
的表达式以及一条信息,当表达式的结果为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)
注意:
当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。