1. 常量和变量
常量的值一旦设定就不能改变,而变量的值可以随意更改。
1.1 声明常量和变量
- 常量和变量必须在使用前声明。
- 用 let 来声明常量。
- 用 var 来声明变量。
let a = 10
var b = 0
// 可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0.0, y = 0.0, z = 0.0
1.2 类型注解
- 当你声明常量或者变量的时候可以加上类型注解(type annotation),说明常量或者变量中要存储的值的类型。
// 声明一个类型为 String ,名字为 message 的变量
var message: String
// 可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型注解
var red, green, blue: Double
1.3 输出常量和变量
let age = 10
let name = "jelly"
// Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中
print("\(b) have \(age) years old.")
2. 分号
- Swift 并不强制要求你在每条语句的结尾处使用分号。
- 有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:
let a = 10 ; print(a)
3. 整数
Swift 提供了8、16、32和64位的有符号和无符号整数类型。
3.1 整数范围
- 访问不同整数类型的 min 和 max 属性来获取对应类型的最小值和最大值:
// minValue 为 0,是 UInt8 类型
let minValue = UInt8.min
// maxValue 为 255,是 UInt8 类型
let maxValue = UInt8.max
3.2 Int
Swift 提供了一个特殊的整数类型 Int,特殊的无符号类型 UInt,长度与当前平台的原生字长相同:
- 在32位平台上,Int 和 Int32 长度相同;UInt 和 UInt32 长度相同。
- 在64位平台上,Int 和 Int64 长度相同;UInt 和 UInt64 长度相同。
尽量不要使用 UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 Int,即使你要存储的值已知是非负的。统一使用 Int 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,
3.3 浮点数
- Double 表示64位浮点数。当需要存储很大或者高精度的浮点数时使用此类型。
- Float 表示32位浮点数。精度要求不高的话可以使用此类型。
4. 类型安全和类型推断
- Swift 是一个类型安全(type safe)的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 String,你绝对不可能不小心传进去一个 Int
- Swift 会使用类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
let a = 10 // 被推测为 Int 类型
let b = 3.1415926 // 被推测为 Double 类型
let c = 3 + 0.1415926 // 被推测为 Double 类型
5. 数值型字面量
- 十进制数:没有前缀
- 二进制数:前缀是 0b
- 八进制数:前缀是 0o
- 十六进制数:前缀是 0x
let decimalInteger = 17
let binaryInteger = 0b10001 // 二进制的17
let octalInteger = 0o21 // 八进制的17
let hexadecimalInteger = 0x11 // 十六进制的17
十进制浮点数也可以有一个可选的指数(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。
let a= 12.1875 // 12.1875
let b= 1.21875e1 // 12.1875
let c= 0xC.3p0 // 12.1875
// 数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量
let h= 000123.456
let i= 1_000_000
let j= 1_000_000.000_000_1
6. 数值型类型转换
6.1 整数转换
不同整数类型的变量和常量可以存储不同范围的数字。
- Int8 类型: -128 --127
- UInt8 类型: 0~255。
如果数字超出了常量或者变量可存储的范围,编译的时候会报错
let a: UInt16 = 2_000
let b: UInt8 = 1
let c= a+ UInt16(b)
6.2整数和浮点数转换
- 整数和浮点数的转换必须显式指定类型
let a= 3
let b= 0.14159
let c = Double(a) + b // pi 等于 3.14159,所以被推测为 Double 类型
- 浮点数到整数的反向转换
let a= 3.14159
let b= Int(a) // integerPi 等于 3,所以被推测为 Int 类型
7. 类型别名
类型别名(type aliases)就是给现有类型定义另一个名字。
typealias GDUInt16 = UInt16
var a = GDUInt16.min // a 现在是 0
8. 布尔值
Swift 有两个布尔常量,true 和 false
let a = true
if a {
print("true")
} else {
print("false")
}
// 输出“true”
9. 元组
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
- http404Error 的类型是 (Int, String),值是 (404, "Not Found")
let http404Error = (404, "Not Found")
- 可以将一个元组的内容分解(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”
注意:
当遇到一些相关值的简单分组时,元组是很有用的。元组不适合用来创建复杂的数据结构。如果你的数据结构比较复杂,不要使用元组,用类或结构体去建模。
9. 可选类型
可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 或者根本没有值。
- Swift 的 Int 类型有一种构造器,作用是将一个 String 值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 "123" 可以被转换成数字 123 ,但是字符串 "hello, world" 不行。
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber) // convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
注意:
因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)
9.1 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,不只是对象类型。
9.2 强制解析
当你确定可选类型确实包含值之后,你可以在可选的 名字后面加一个感叹号(!)来获取值 。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping)。
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
注意
使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值。
9.3 可选绑定
- 使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。
// 如果 someOptional 包含一个值,创建一个叫做 constantName 的新常量并将可选包含的值赋给它。”
if let constantName = someOptional {
// 如果转换成功,constantName 常量可以在 if 语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。
}
- 可包含多个可选绑定或多个布尔条件在一个 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 语句外且在语句后才能获取到值。
9.4 隐式解析可选类型
- 有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)
- 声明:把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。
- 一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号
注意
如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。
10. 错误处理
使用 错误处理(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] 作为参数。
11. 断言和先决条件
- 断言和先决条件是在运行时所做的检查。
- 断言帮助你在开发阶段找到错误和不正确的假设。断言仅在调试环境运行。
- 先决条件帮助你在生产环境中探测到存在的问题。先决条件则在调试环境和生产环境中运行。
- 在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
11.1使用断言进行调试
- 可以调用 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.")
}
11.2 强制执行先决条件
当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。
例如: 使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。
- 你可以使用全局 precondition(::file:line:) 函数来写一个先决条件。向这个函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:
// 在一个下标的实现里...
precondition(index > 0, "Index must be greater than zero.")
-
你可以调用 preconditionFailure(_:file:line:) 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。
注意 如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,fatalError(_:file:line:) 函数总是中断执行,无论你怎么进行优化设定。
你能使用 fatalError(_:file:line:) 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError("Unimplemented")作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。