Swift-基础部分
前言
Swift是一门新的适用于iOS,macOS,watchOS,tvOS开发的编程语言。尽管如此,Swift的许多部分和C和OC是相似的。
对于所有的C和OC基础类型,Swift都有其自己的版本,如Int
代表整形,Double
和Float
代表浮点型,Bool
代表逻辑值,String
代表文本数据。Swift同样提供了三种最主要的集合类型(Array,Set,Dictionary)的强大版本,这些将在集合类型章节介绍。
像C一样,Swift使用变量来进行存储并通过标识名来进行索引。Swift同样对值不可变的变量进行了扩展,这就是常量,Swift的常量要比C的常量更加强大。常量贯穿Swift的使用,当你使用无需改变的值时,常量使得代码更加安全,简洁。
除了熟悉的类型以外,Swift提供了C没有的高级类型,比如元组。元组可以创建并传递一组值。你可以使用元组在一个函数中返回多个值作为一个单独的组合值。
Swift同样引进了可选型,用来处理值不存在的情况。可选值意味着:或者存在一个值,它等于多少,或者根本就没有值。可选型像OC的nil指针一样,但是可选型可以代表任意类型,不像OC只能代表类。相比于OC的nil指针,可选型不但安全,更有表现力,而且它是Swift许多最强大类型的核心。
Swift是一门类型安全的语言,这意味着语言层面就帮助你清楚地知道你所面对的值的类型。如果你的代码需要String
,类型安全机制会避免你错误的传递一个Int
。同样的,类型安全机制也会防止你意外的传入一个可选型String
。类型安全机制帮助你在开发阶段尽早的捕获并解决错误。
注意,目前简书不支持文章内跳转,所以文章中蓝色文字点击是无法跳转的,当支持此功能时会加上。
目录
- 常量和变量
- 注释
- 分号
- 整数
- 浮点数
- 类型安全和类型推断
- 数值型字面量
- 数值型类型转换
- 类型别名
- 布尔值
- 元组
- 可选型
- 错误处理
- 断言
常量和变量
常量和变量把一个名字(例如maximumNumberOfLoginAttempts
或者welcomeMessage
)和一个确定类型的值关联起来。常量一旦被赋值就不能修改,但是变量的值可以修改。
声明常量和变量
常量和变量在使用前必须声明。声明常量使用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
声明中的冒号代表着“xx类型的xx”,所以以上的代码可以解读为:
“声明一个名为welcomeMessage
的String
类型变量”。
这个“字符串类型的xx”短语意味着“可以存储任何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 is now "Bonjour!"
与变量不同,常量的值一旦设定就不可更改。否则编译时会报错。
let languageName = "Swift"
languageName = "Swift++"
// This is a compile-time error: languageName cannot be changed.
打印常量和变量
可以使用print(_:separator:terminator:)
函数打印常量或变量的当前值:
print(friendlyWelcome)
// Prints "Bonjour!"
print(_:separator:terminator:)
函数是一个全局函数,它可以恰当的输出一个或多个值。例如,在Xcode中它会在控制台输出。separator
和terminator
参数有默认值,所以调用函数时可以忽略。默认的,函数输出后会换行。如果想要不换行输出,terminator
传入空字符串即可,例如print(someValue, terminator: "")
。想了解关于默认参数的信息,可以查看默认参数值。
在一个长字符串中,Swift使用字符串插值 将常量或者变量以占位符的方式包括进来,这会提示Swift将插值替换为常量或者变量的当前值。方式为将名字放进圆括号中,并在左圆括号前添加反斜杠:
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// Prints "The current value of friendlyWelcome is Bonjour!"
注意
字符串插值的所有选项可见字符串插值
注释
使用注释可以在你的代码中引入非执行的文本,作为自己的注解或者提醒。注释在代码编译阶段会被Swift编译器忽略。
Swift的注释和C很像。单行注释以两个斜杠开始:
// This is a comment.
多行注释以一个斜杠和星号开始,以一个星号和斜杠结束:
/* This is also a comment
but is written over multiple lines. */
与C的多行注释不同,Swift的多行注释可以嵌套在其它的多行注释中。
/* This is the start of the first multiline comment.
/* This is the second, nested multiline comment. */
This is the end of the first multiline comment. */
嵌套的多行注释可以快速轻松的注释掉大块代码,即使代码中已经包含了多行注释。
分号
与其它大部分语言不同,Swift不要求每句话以分号结尾,尽管如此,如果你喜欢也可以添加分号。但是,如果想在一行书写多个语句,那么分号就是必须的:
let cat = "????"; print(cat)
// Prints "????"
整数
整数是没有小数部分的数字,例如42
和-23
。整数分为有符号的(正数,零或者负数)和无符号的(正数或者零)。
Swift提供8位,16位,32位和64位的有符号和无符号的整数。这些整形和C有相似的命名规范,8位无符号的整形为UInt8
,32位有符号的整形为Int32
。和所有Swift的类型一样,这些整形有大写的名字。
整形边界
可以通过整型的min
和max
属性获取到每一种整型的最小值和最大值:
let minValue = UInt8.min // minValue is equal to 0, and is of type UInt8
let maxValue = UInt8.max // maxValue is equal to 255, and is of type 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是门类型安全的语言。一门类型安全的语言让你清楚地知道代码中值的类型。如果你的代码期望String
类型,你绝不可能错误的传入一个Int
类型。
因为Swift是类型安全的,所以编译时它会执行类型检查并把不符合的类型标记为错误。这使得你可以在开发阶段尽早的捕获并修复错误。
类型检查让你面对不同的值类型时可以避免错误。但是这并不意味着你必须明确每一个你声明的常量或者变量的类型。如果你不明确你需要值的类型,Swift使用类型推断去查找出合适的类型。类型推断使得编译器在编译阶段通过检查你提供的值自动的推断一段表达式的类型。
因为类型推断,Swift相比于C或者OC需要极少的类注释。虽然如此,常量和变量还是被明确的定义了类型,只不过指定类型的大部分工作编译器已经帮你做了。
当你声明一个常量或者变量并赋初始值时,类型推断变得特别有用。当声明一个常量或者变量,在给他们赋值字面量时,类型推断就被触发了。(字面量就是直接在你源代码中出现的值,例如下面例子中的32
和3.14159
)。
例如,你把一个字面量42
赋给了一个新的常量但是没有说明它的类型,Swift就会推断你想让这个常量为Int
型,因为你用一个看起来像整型的数值初始化了它。
let meaningOfLife = 42
// meaningOfLife is inferred to be of type Int
同样的,如果你不明确的定义一个浮点型字面量的类型,Swift会推断你想创建一个Double
型:
let pi = 3.14159
// pi is inferred to be of type Double
当推断一个浮点值的类型时,Swift总是选择Double
而不是Float
。
如果你在一个表达式中集合了整数和浮点数字面量,Swift会从上下文中推测出Double
类型。
let anotherPi = 3 + 0.14159
// anotherPi is also inferred to be of type Double
字面量3
本身没有明确的类型,但是浮点型字面量作为加法表达式的一部分的出现使得字面量3
被推断为Double
型。
数值型字面量
整型字面量可以写成这样:
- 十进制数,没有前缀
- 二进制数,前缀
0b
- 八进制数,前缀
0o
- 16进制数,前缀
0x
以下所有的整型字面量都有一个10进制的值17
:
let decimalInteger = 17
let binaryInteger = 0b10001 // 17 in binary notation
let octalInteger = 0o21 // 17 in octal notation
let hexadecimalInteger = 0x11 // 17 in hexadecimal notation
浮点数字面量可以是十进制(没有前缀)的或者16进制(前缀0x
)的。他们在小数点的两边必须有值(或者一个16进制的值)。十进制的浮点数可以有一个可选择的指数,表示为大写或者小写的e
;16进制的浮点数必须有一个指数,表示为大写或者小写的e
。
对于指数为exp
的十进制数,值为基数乘以10^exp:
-
1.25e2
means 1.25 x 10^2, or125.0
. -
1.25e-2
means 1.25 x 10^-2, or0.0125
.
对于指数为exp
的十六进制数,值为基数乘以2^exp:
-
0xFp2
means 15 x 2^2, or60.0
. -
0xFp-2
means 15 x 2-2, or3.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
-128
范围的数据,然而UInt8
可以存储0
-255
。如果一个值不在一个指定大小的整型类型范围编译时会报错。
let cannotBeNegative: UInt8 = -1
// UInt8 cannot store negative numbers, and so this will report an error
let tooBig: Int8 = Int8.max + 1
// Int8 cannot store a number larger than its maximum value,
// and so this will also report an error
因为每一个值类型可以存储不同的值范围,所以不同的情况需要选择不同的值类型转换。这种选择方式避免了潜在的转换错误并且使得代码中的类型转换变得明确。
将一种指定的值的类型转化为另外一种,需要用已知值初始化一个期望类型的新值。在下面的例子中,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
提供的构造器支持的类型。扩展已知类型使之提供可以接收新的类型,包括自定义类型的构造器的内容会在扩展介绍。
整形和浮点型转换
整型和浮点型之间的转换必须是显示的:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double
这里,常量three
的值被用来创建一个新的Double
型的值,这样加法的两边类型相同。没有这种转化作为替代,加法是不被允许的。
浮点型转化为整型也必须是显示的。可以用Double
或者Float
型值来初始化一个整形:
let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type 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
一样,无需声明常量或者变量为Bool
型如果在创建它们的时候赋值为true
或者false
。当使用其它已知类型的值初始化常量或者变量时,类型推断让Swift的代码更加简洁易读。
当书写条件语句时,布尔值极其有用,例如if
声明:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// Prints "Eww, turnips are horrible."
如if
之类的条件声明会在控制流详细介绍。
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
的比较会在基础运算符讨论。
像Swift中其它类型安全的例子一样,这种方式避免了意外的错误并且确保一段特殊的代码的意图是明确的。
元组
元组将多个值组合进一个单独的组合值。元祖中的值可以是任意类型,每个值的类型无需相同。
在下面的例子中,(404, "Not Found")
是一个描述HTTP状态码的元组。一个HTTP状态码是当请求一个网页时服务器返回的特殊值。当请求一个不存在的网页时会返回404 Not Found
状态码。
let http404Error = (404, "Not Found")
// http404Error is of type (Int, String), and equals (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)")
// Prints "The status code is 404"
print("The status message is \(statusMessage)")
// Prints "The status message is Not Found"
如果只需要某部分的元组值,可以在分解时使用下划线忽略其它部分值:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// Prints "The status code is 404"
获取元组的某个元素可以使用以0开头的索引:
print("The status code is \(http404Error.0)")
// Prints "The status code is 404"
print("The status message is \(http404Error.1)")
// Prints "The status message is Not Found"
当定义元组时可以给元素命名:
let http200Status = (statusCode: 200, description: "OK")
如果命名了元组中的元素,那么可以通过元素名获取对应的值:
print("The status code is \(http200Status.statusCode)")
// Prints "The status code is 200"
print("The status message is \(http200Status.description)")
// Prints "The status message is OK"
元组作为函数的返回值时是极其有用的。一个用来检索网页的函数也许会返回(Int, String)
元组类型来描述网页检索结果的成功或者失败。通过返回由两个不同类型的特殊值构成的元组,相比于返回一个单一类型的单一值,函数对输出提供了更有用的信息。想获取更多信息,查看多个返回值的函数。
注意
元组对于关联值的临时组合是有帮助的。它并不适于创建复杂的数据结构。如果你的数据结构可能用于持久化而不是临时存储,把它定义为类或者结构体而不是元组。想获取更多信息,查看类和结构体。
可选型
可以在值缺失的情况下使用可选型。一个可选型有两种可能性:或者有一个值,可以通过解包可选型获取到这个值,或者根本就没有值。
注意
可选型这个概念在C或者OC中并不存在。OC中最接近可选型的例子是一个函数可以返回nil
的能力,如果不返回nil
,它会返回一个对象,nil
意味着一个合法值的缺失。但是nil
只适用于对象,而像结构体,基本的C类型或者枚举并不适用。对于这些类型,OC方法通常会返回一个特殊值(例如NSNotFound
)来指明值的缺失。这种方式假设方法的调用者知道有一个特殊值需要测试并且记得检查它。Swift的可选型让你可以表示任意类型的值缺失,而无需特殊的常量。
下面的例子展示了可选型是如何被用来处理一个值的缺失的。Swift的Int
型有一个构造器可以尝试将一个String
值转化为Int
值。但是并不是所有的字符都能被转化为整形。字符"123"
可以被转化成数值型123
,但是字符hello, world
并没有明显的数值可以用来转化。
下面的例子使用构造器尝试将一个String
转化为Int
:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"
因为初始化可能会失败,所以它返回一个可选型的Int
,而不是Int
。一个可选型的Int
写成Int?
而不是Int
。这个问号表示它包含的值是可选型的,意味着它可能包含某个Int
值,或者根本就不包含任何值。(它不可能包含任何其它的东西,比如Bool
值或者String
值。它或者是一个Int
,或者什么都不是)。
nil
想把一个可选型变量设置为无值的状态,可以赋给它特殊值nil
;
var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value
注意
nil
不能用于非可选型的常量或者变量。如果代码中的常量或者变量需要在某些情况下处理值缺失的情况,永远声明它为适当类型的可选型值。
如果定义了一个没有默认值的可选型变量,变量会自动被赋值为nil
:
var surveyAnswer: String?
// surveyAnswer is automatically set to nil
注意
Swift的nil
和OC的nil
不同。在OC中,nil
是一个指向不存在对象的指针。在Swift中,nil
不是一个指针--它是一个确定类型的值的缺失。任意类型的可选型都可以被赋值为nil
,不只是对象类型。
条件声明和强制解包
可以在if
声明中通过比较可选型和nil
来判断可选型是否包含一个值。通过使用==
运算符或者!=
运算符来进行比较。
如果一个可选型有一个值,那么这个可选型会被认为不等于nil
。
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// Prints "convertedNumber contains some integer value."
一旦你确定可选型包含一个值,可以通过在可选型的名字后添加一个叹号来获取潜在值。叹号实际上意味着:我知道这个可选型肯定有一个值,请使用它吧。这就是可选型值的强制解包。
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."
想获取更多关于if
声明的信息,可以查看控制流;
注意
尝试使用叹号去获取一个不存在的可选型值会触发运行时错误。在使用叹号强制解包前一定要确保可选型包含一个非nil
值。
可选绑定
可以使用可选绑定 判断一个可选型是否包含一个值,如果包含,可以将这个值当做一个临时的常量或者变量使用。可以结合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
或者任何布尔条件为假,那么整个if
声明的条件会被认为为假。以下的if
声明是等价的。
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
// Prints "4 < 42 < 100"
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
}
}
// Prints "4 < 42 < 100"
注意
在一个if
声明中通过可选绑定创建的常量或者变量只在if
声明的大括号内有效。与此相反,通过guard
声明创建的常量和变量在guard
声明以后的代码有效,详见提前退出。
隐式解包可选型
综上所述,可选型表示一个常量或者变量允许有一个”空值“。可以在if
声明中检测可选型是否有值,也可以通过可选绑定来条件解包获取可选型的值,如果值存在的话。
有些时候从程序的结构中我们明确知道一个可选型在第一次被赋值后会一直有值。在这些情况下,去掉每次使用可选型都要检查并且解包的步骤就变得非常有用,因为它可以被安全的假设在任何时候都有值。
这些类型的可选型定义为隐式解包可选型。通过在类型后添加一个叹号而不是问号来书写一个隐式可选型。
如果一个可选型第一次被赋值后,它的值是明确存在的,并且此后的任何时候它的值都是肯定存在的,这种情况下隐式可选型是十分有用的。Swift中隐式可选型大多使用在类的初始化中,详见若引用和隐式解包可选型属性。
一个隐式解包可选型本质上是一个普通的可选型,但是也可以当做非可选型值使用,无需每次使用时都去解包可选型值。下面的例子展示了一个可选型字符和一个隐式可选型字符当获取它们的解包值时不同的方式:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for an exclamation mark
你可以认为一个隐式解包可选型有权限在每次被使用时都会自动解包。每次使用时不是在可选型的名字后添加叹号,而是在你声明它的时候在可选型类型后添加叹号。
注意
如果一个隐式可选型是nil
,这时尝试获取它的解包值会触发运行时错误。结果和你在一个不包含值的可选型后添加叹号一样。
你可以仍然把一个隐式可选型当做一个普通的可选型,检查它是否包含一个值:
if assumedString != nil {
print(assumedString)
}
// Prints "An implicitly unwrapped optional string."
同样可以结合可选绑定来使用一个隐式可选型,在一个声明中检查并解包它的值:
if let definiteString = assumedString {
print(definiteString)
}
// Prints "An implicitly unwrapped optional string."
注意
当一个变量有可能稍后变成nil
时,不要使用隐式可选型。如果在一个变量的生命周期中需要检查它是否为nil
,记得永远使用普通的可选型。
错误处理
可以使用错误处理 来响应你的程序运行中也许会遇到的错误状况。
可选型可以使用一个值的存在或者不存在来表达一个函数的成功或者失败,与之对应的是,错误处理允许你判断错误的根本原因,如有必要,可以将错误传递到你程序的另一个地方。
当一个函数遭遇一种错误状况,它会抛出错误。接下来函数的调用者可以捕获错误并且适当响应:
func canThrowAnError() throws {
// this function may or may not throw an error
}
一个函数可以通过在它的声明中添加throws
关键字来表明可以抛出错误。当你调用一个可能抛出错误的函数时,考虑在表达式前添加try
关键字。
Swift自动将错误传播出它们当前的范围直到它们被一个catch
字句处理。
do {
try canThrowAnError()
// no error was thrown
} catch {
// an error was thrown
}
一个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
范式捕获的相关值。
抛出,捕获和传递错误会在错误处理详细讲述。
断言
在某些情况下,如果不满足一个特殊的情况,代码通常不会继续运行。在这些情况下,你可以触发代码中的一个断言 来结束代码执行并提供一个debug值缺失或无效原因的机会。
使用断言调试
断言会在运行时检查一个布尔条件是否为true
。字面上来看,断言用来“断定”一个条件是否为真。在继续执行代码前,使用断言确保满足必要的条件。如果条件为真,代码正常执行;如果为假,代码执行结束,APP停止运行。
如果代码在debug环境运行时触发了一个断言,例如在Xcode中创建运行一个APP,你可以发现非法状态发生的确切位置并且查询断言被触发时你的APP状态。此外,断言本身会允许你提供一条合适的debug信息。
可以通过Swift标准库的全局函数assert(_:_:file:line:)
来调用断言。向这个函数传递一个结果为true
或者false
的表达式和一条信息,这条信息会在条件为false
时展示。
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// this causes the assertion to trigger, because age is not >= 0
在这个例子中,代码只在age >= 0
为true
时继续执行,这意味着,age
的值必须是非负的。如果age
的值是负数,像上面的代码一样,age >= 0
结果会为false
,断言被触发,程序被终止。
断言的信息如有需要可以忽略,如下所示:
assert(age >= 0)
注意
断言在优化编译的情况下是无效的,例如在Xcode运行一个Release版本。
何时使用断言
当一个条件可能为假时使用断言,但是最终必须为真,这样你的程序才能继续运行。断言合适的场景包括:
- 一个整形的下标索引被传入一个自定义的下标实现中,但是这个下标索引可能太小或者太大。
- 一个值被传入了一个函数,但是非法的值可能导致函数无法完成它的任务。
- 一个可选型的值当前是
nil
,但是接下来的代码想要执行成功需要一个非nil
值。
注意
断言会导致程序终止运行,所以它并不是设计你的代码来处理非法的条件可能会发生的情况的替代方式。但是在你的APP发布之前,在非法条件可能存在的地方,一个断言是确保这些情况在开发阶段被高亮并注意到一个高效的方式。