前言:
本篇文章的目的,在于记录学习过程,敦促自己,方便查看。练习工具:Playground
学习网站: swift51
本页内容包括:
常量和变量
常量的值一旦设定就不能改变,而变量的值可以随意更改。
声明常量和变量
常量和变量必须在使用前声明,用 let 来声明常量,用 var 来声明变量。
下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:
let maximumNumberOfLoginAttemps = 10 var currentLoginAttempt = 0
这两行代码可以被理解为:
1.0 声明一个名字是 maximumNumberOfLoginAttempts 的新常量,并给它一个值 10 。 2.0 声明一个名字是 currentLoginAttempt 的变量并将它的值初始化为 0 。
在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。
你可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0.0, y = 0.0, z = 0.0
类型标注
当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要
添加类型标注
,需要在常量
或者变量名后面
加上一个冒号
和空格
,然后加上类型名称
。这个例子给 welcomeMessage 变量添加了类型标注,表示这个变量可以存储 String 类型的值:
var welcomeMessage: String
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:
1.0 声明一个类型为 String ,名字为 welcomeMessage 的变量。 2.0 类型为 String ”的意思是“可以存储`任意` String 类型的值
welcomeMessage 变量现在可以被设置
成任意字符串
: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) // 输出 "Bonjour!"
print(_:separator:terminator:)
是一个用来输出一个或多个值到适当输出区的全局函数。如果你用Xcode,print(_:separator:terminator:)
将会输出内容到“console
”面板上。separator
和terminator
参数具有默认值
,因此你调用这个函数的时候可以忽略
它们。默认情况下,该函数通过添加换行符
来结束
当前行。如果不想换行
,可以传递
一个空字符串
给terminator
参数--例 如,print(someValue, terminator:"")
。关于参数默认值的更多信息,请参考默认参数值。Swift 用
字符串插值(string interpolation)的方式
把常量名或者变量名当做
占位符加入
到长字符串中,Swift 会用当前常量或变量的值替换
这些占位符。将常量
或变量名
放入圆括号中
,并在开括号前
使用反斜杠
将其转义
:print("The current value of friendlyWelcome is \(friendlyWelcome)") // 输出 "The current value of friendlyWelcome is Bonjour!
注意
字符串插值所有可用的选项,请参考字符串插值。
注释
请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠
(//)
作为起始标记:// 这是一个注释
你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号
(/*)
,终止标记为一个星号后跟随单个正斜杠(*/)
:/* 这是一个, 多行注释 */
与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再
嵌套
成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:/* 这是第一个多行注释的开头 /* 这是第二个被嵌套的多行注释 */ 这是第一个多行注释的结尾 */
通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。
分号
与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号(;)
,当然,你也可以按照你自己的习惯添加分号。有一种情况下必须
要用分号
,即你打算在同一行内写多条独立的语句
:let cat = "🐱";print(cat) // 输出 "🐱"
整数
整数就是没有小数部分的数字,比如 42 和 -23 。整数可以是 有符号(正、负、零)或者 无符号(正、零)。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 类型
min
和max
所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。
Int
一般来说,你不需要专门指定整数的长度。Swift
提供了一个特殊的整数类型Int
,长度与当前平台的原生字长相同:
- 在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
是一个类型安全(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 // meaningOfLife 会被推测为 Int 类型
同理,如果你
没有给浮点字面量标明类型
,Swift
会推断你想要的是Double
:let pi = 3.14159 // pi 会被推测为 Double 类型
当推断浮点数的类型时,
Swift
总是会选择Double
而不是Float
。如果表达式中
同时出现
了整数
和浮点数
,会被推断
为Double
类型:let anotherPi = 3 + 0.14159 // anotherPi 会被推测为 Double 类型
原始值
3
没有显式声明类型
,而表达式中出现了一个浮点字面量
,所以表达式会被推断
为Double
类型。
数值型字面量
整数字面量
可以被写作:1. 一个十进制数,`没有前缀` 2. 一个二进制数,前缀是`0b` 3. 一个八进制数,前缀是`0o` 4. 一个十六进制数,前缀是`0x`
下面的所有整数字面量的十进制值都是17:
let decimalInteger = 17 let binaryInteger = 0b10001 // 二进制的17 let octalInteger = 0o21 // 八进制的17 let hexadecimalInteger = 0x11 // 十六进制的17
浮点字面量
可以是十进制
(没有前缀)或者是十六进制
(前缀是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 // 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
相加,因为数字字面量本身没有明确的类型
。它们的类型只在编译器需要求值
的时候被推测
。
类型别名
类型别名(type aliases)
就是给现有类型定义另一个名字
。你可以使用typealias
关键字来定义类型别名
。当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
typealias AudioSample = UInt16
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
var maxAmplitudeFound = AudioSample.min // maxAmplitudeFound 现在是 0
本例中,
AudioSample
被定义为UInt16
的一个别名
。因为它是别名,AudioSample.min
实际上是UInt16.min
,所以会给maxAmplitudeFound
赋一个初值0。
布尔值
Swift
有一个基本的布尔(Boolean)
类型,叫做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.") } // 输出 "Eww, turnips are horrible."
条件语句,例如
if
,请参考控制流。如果你在需要使用
Bool
类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:let i = 1 if i { // 这个例子不会通过编译,会报错 }
然而,下面的例子是合法的:
let i = 1 if i == 1 { // 这个例子会编译成功 }
i == 1
的比较结果是Bool
类型,所以第二个例子可以通过类型检查。类似i == 1
这样的比较,请参考基本操作符。和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。
元组
元组(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 code 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
注意:
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 contains some integer value."
当你确定
可选类型确实包含值之后
,你可以在可选的名字后面
加一个感叹号(!)
来获取值
。这个惊叹号
表示“我知道这个可选有值,请使用它。
”这被称为可选值的强制解析(forced unwrapping)
:if convertedNumber != nil { print("convertedNumber has an integer value of \(convertedNumber!).") } // 输出 "convertedNumber has an integer value of 123."
更多关于
if
语句的内容,请参考控制流。
注意:
使用!
来获取
一个不存在
的可选值
会导致运行时错误
。使用!
来强制解析值之前
,一定要确定可选包含一个非 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") } // 输出 "'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]
作为参数。抛出,捕捉,以及传播错误会在错误处理章节详细说明。
断言和先决条件
断言和先决条件是在
运行时所做的检查
。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为true(真)
,则代码像往常一样继续执行。如果布尔条件评估结果为false(假)
,程序的当前状态是无效的,则代码执行结束,应用程序中止。你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。
除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的错误处理不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。
使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。
断言和先决条件的不同点是,他们什么时候进行状态检测:
断言仅在调试环境运行
,而先决条件
则在调试环境和生产环境中运行
。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
使用断言进行调试
你可以调用
Swift
标准库的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)
如果代码已经检查了条件,你可以使用 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.")
你可以调用
precondition(_:_:file:line:)
方法来表明出现了一个错误,例如,switch
进入了default
分支,但是所有的有效值应该被任意一个其他分支(非 default
分支)处理。注意:
如果你使用unchecked
模式(-Ounchecked)
编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为true(真),他将优化你的代码。然而,fatalError(_:file:line:)
函数总是中断执行,无论你怎么进行优化设定。你能使用
fatalError(_:file:line:)
函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上fatalError("Unimplemented")
作为具体实现。因为fatalError
不会像断言和先决条件那样被优化掉
,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。