Swift 6 学习笔记(二)The Basics

这篇笔记也是同步 Swift 6 官方教程中的第二篇 《The Basics》,这篇博客中的大部分内容在第一篇中已经涉及,这篇可以被认为是基础类型的的补充篇,多了很多说明信息。


Swift 提供了许多基本数据类型,包括用于整数的 Int、用于浮点值的 Double、用于布尔值的 Bool 以及用于文本的 String。Swift 还提供了三种主要集合类型(数组、集合、字典)。

Swift 使用 变量 通过标识名称来存储和引用值,同时广泛使用 值不可更改的变量 即常量,目的是让代码更安全、意图更清晰。

除了常见的类型外,Swift 还引入了元组等高级类型。通过元组可以创建和传递一组值,使用元组将函数中的多个值作为单个复合值返回。

可选类型 用来处理值的缺失的情况,可选类型表示"存在一个值"或"根本没有值"。可选值确保代码在使用值之前始终检查该值是否缺失,并且保证非可选值永远不会缺失。

Swift 是一门 安全的语言,这意味着它有以下几个特性:

  1. 可以在开发过程中尽早发现和修复几类错误,并保证某些类型的错误不会发生;
  2. 能够清楚地了解代码所处理值的类型,如果部分代码需要字符串,可以防止错误地将整数传递给它;
  3. 确保只能使用有效数据,而不是未初始化的内存或未初始化的对象;

Swift 在构建代码时执行大部分安全检查,并且在某些情况下会在代码运行时执行额外的检查。


1. Constants and Variables 常量与变量

常量和变量将一个名称(例如 maximumNumberOfLoginAttemptsWelcomeMessage)与特定类型的值(例如数字 10 或字符串"Hello")关联起来。常量的值一旦设置就无法更改,而变量的值可以在将来设置为其他值。

1.1 Declaring Constants and Variables 常量与变量声明

常量和变量必须在使用前声明 。使用 let 关键字声明常量,使用 var 关键字声明变量。以下示例展示了如何使用常量和变量来跟踪用户登录尝试次数:

swift 复制代码
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

这段代码可以理解为:声明一个名为 maximumNumberOfLoginAttempts 的新常量,并赋值为 10;声明一个名为 currentLoginAttempt 的新变量,并赋值为 0

在此示例中,允许的最大登录尝试次数被声明为常量,因为最大值永远不会改变。当前登录尝试计数器被声明为变量,因为该值必须在每次登录尝试失败后递增。

如果代码中存储的值不会改变,应始终使用 let 关键字将其声明为常量。变量仅用于存储会改变的值。

声明常量或变量时,可以像上面的示例一样在声明过程中赋值,也可以在程序的后续阶段赋值,只要确保在第一次使用之前它已经有一个值即可。

swift 复制代码
var enviroment = "development"
let maximumNumberOfLoginAttempts: Int

if enviroment == "development"{
    maximumNumberOfLoginAttempts = 100
}else{
    maximumNumberOfLoginAttempts = 10
}

在此示例中,最大登录尝试次数是一个常量;在开发环境中其值为 100;在其他环境中其值为 10。if 语句的两个分支都使用某个值初始化 maximumNumberOfLoginAttempts,从而确保该常量始终获得一个值。

在同一行中声明多个常量或变量,并用逗号分隔:

swift 复制代码
var x = 0.0, y = 0.0, z = 0.0

1.2 Type Annotations 类型注解

声明常量或变量时可以提供类型注解 ,以明确该常量或变量可以存储的值类型。编写类型注解的方法是,在常量或变量名称后添加一个冒号 :,然后跟一个空格,最后跟要使用的类型名称。

此示例为名为welcomeMessage 的变量提供了一个类型注解,以指示该变量可以存储字符串值:

swift 复制代码
var welcomeMessage: String

上面的代码可以理解为:声明一个名为 welcomeMessage 的变量,其类型为String。

welcomeMessage 变量设置为任何字符串值而不会出现错误:

swift 复制代码
var welcomeMessage: String

welcomeMessage = "Hello"

可以在同一行上定义多个相同类型的相关变量,用逗号分隔,并在最终变量名后添加单个类型注释:

swift 复制代码
var red, green, blue: Double

1.3 Naming Constants and Variables 变量与常量命名

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

swift 复制代码
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"

但常量和变量名不能包含空格字符、数学符号、箭头、私有的 Unicode 标量值或线条和框绘制字符。它们也不能以数字开头,但数字可以出现在名称的其他位置。

一旦声明了某种类型的常量或变量,就不能再用相同的名称声明它,也不能将其更改为存储不同类型的值。也不能将常量更改为变量,也不能将变量更改为常量。

可以将现有变量的值更改为兼容类型的其他值 。在此示例中,friendlyWelcome 的值从"Hello!"更改为"Bonjour!":

swift 复制代码
var friendlyWelcome = "Hello"
friendlyWelcome = "Bonjour"

与变量不同,常量的值一旦设置就无法更改。尝试更改代码时,编译时会报错:

swift 复制代码
let languageName = "Swift"
languageName = "Swift++"    // 报错,常量不可修改

1.4 Printing Constants and Variables 输出变量与常量

可以使用 print(_:separator:terminator:) 函数打印常量或变量的当前值:

swift 复制代码
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"

print(friendlyWelcome)		// Bonjour!

print(_:separator:terminator:) 函数是一个全局函数,它将一个或多个值打印到适当的输出。例如,在 Xcode 中,print(_:separator:terminator:) 函数将其输出打印在 Xcode 的"控制台"窗格中。分隔符和终止符参数具有默认值,因此可以在调用此函数时省略它们。默认情况下,该函数会通过添加换行符来终止其打印的行。如果想要打印一个不带换行符的值,需要传递一个空字符串作为终止符 - 例如,print(someValue, terminator: "")

swift 复制代码
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"

print(friendlyWelcome, terminator: "")

Swift 使用字符串插值,将常量或变量的名称作为占位符包含在较长的字符串中,并提示 Swift 将其替换为该常量或变量的当前值。将名称括在括号中,并在左括号前使用反斜杠进行转义:

swift 复制代码
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"

print("The current value of friendlyWelcome is \(friendlyWelcome)")
// The current value of friendlyWelcome is Bonjour!

2. Comments 注释

2.1 Single line comments 单行注释

使用注释在代码中添加不可执行的文本,作为注释或提醒,编译器在编译代码时会忽略注释。

Swift 中的注释与 C 语言中的注释非常相似。单行注释以两个正斜杠 // 开头:

swift 复制代码
// This is a comment.

2.2 Multi line comments 多行注释

多行注释以斜杠加星号 /* 开头,以星号加斜杠 */ 结尾:

swift 复制代码
/* This is also a comment
but is written over multiple lines. */

2.3 Multi line inside comments 整块注释

与 C 语言中的多行注释不同,Swift 中的多行注释可以嵌套在其他多行注释中。编写嵌套注释的方法是:先开始一个多行注释块,然后在第一个注释块内开始第二个多行注释。然后,第二个注释块结束,接着是第一个注释块:

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. */

嵌套多行注释能够快速轻松地注释掉大块代码,即使代码已经包含多行注释,这一点比 C++ 的逻辑方便太多了。


3. Semicolons 分号

Swift 并不要求在代码中的每个语句后都强制添加分号 ; 即便加上也可以通过编译。如果想在一行中编写多个单独的语句,则必须使用分号:

swift 复制代码
let cat = "🐱"; print(cat)
// 🐱

4. Integers 整型

整数是不带小数部分的整数,例如 42 和 -23。整数可以是有符号整数(正数、零或负数),也可以是无符号整数(正数或零)。

Swift 提供 8、16、32 和 64 位的有符号整数和无符号整数。这些整数遵循与 C 语言类似的命名约定,即 8 位无符号整数的类型为 UInt8,32 位有符号整数的类型为 Int32。与 Swift 中的所有类型一样,这些整数类型的名称均采用大写字母。

4.1 Integer Bounds 整型范围

使用 minmax 属性访问每个整数类型的最小值和最大值:

swift 复制代码
let minValue = UInt8.min
let maxValue = UInt8.max

print(minValue, maxValue)	// 0 255

这些属性可以与相同类型的其他值一起在表达式中使用。

4.2 Int 整型

大多数情况下,无需在代码中指定整数的大小。Swift 提供了一个额外的整数类型 Int,其大小 与当前平台的原生字长相同

  • 在 32 位平台上,Int 的大小与 Int32 相同。
  • 在 64 位平台上,Int 的大小与 Int64 相同。

除非特殊情况,否则应该始终使用 Int 表示整数值,这样有助于提高代码的一致性和互操作性。即使在 32 位平台上,Int 也可以存储 − 2 , 147 , 483 , 648 -2,147,483,648 −2,147,483,648 到 2 , 147 , 483 , 647 2,147,483,647 2,147,483,647 之间的任何值,并且足以容纳许多整数范围。

4.3 UInt 无符号整型

Swift 还提供了一个无符号整数类型 UInt,其大小与当前平台的原生字长相同:

  • 在 32 位平台上,UInt 的大小与 UInt32 的大小相同。
  • 在 64 位平台上,UInt 的大小与 UInt64 的大小相同。

5. Floating-Point Numbers 浮点型

浮点数是带有小数部分的数字,例如 3.14159 3.14159 3.14159、 0.1 0.1 0.1 和 − 273.15 -273.15 −273.15。浮点类型可以表示比整数类型更广泛的值,并且可以存储比 Int 类型更大或更小的数字。Swift 提供了两种有符号浮点数类型:

  • Double 表示 64 位浮点数。
  • Float 表示 32 位浮点数。

6. Type Safety and Type Inference 类型安全与类型推断

Swift 中的 每个值都有类型,可以使用类型注解明确指定类型,或者由 Swift 根据初始值推断类型。每个提供值的地方,该值的类型都必须与使用它的地方匹配,如果代码的某个部分需要一个字符串,就不能将其赋值一个整数,这种检查使 Swift 成为一种类型安全的语言。

类型安全的语言要求明确代码所处理值的类型,这样一种类型的值永远不会被隐式转换为另一种类型。但是某些类型可以显式转换。在编译时任何不匹配的类型标记为错误。

类型检查并不意味着必须明确声明的每个常量和变量的类型,如果未指定所需值的类型,Swift 会使用类型推断来计算出合适的类型,类型推断使编译器能够在编译代码时自动推断特定表达式的类型。

由于类型推断,Swift 所需的类型声明比 C 或 Objective-C 等语言少得多,常量和变量仍然需要显式指定类型。

类型推断通常是通过在声明常量或变量时为其赋值(或字面量)来实现的:

swift 复制代码
let meaningOfLife = 42	// 编译器推断为 Int
let pi = 3.14159 		// 编译器推断为 Double		

Swift 在推断浮点数的类型时始终选择 Double 而不是 Float,如果在表达式中组合使用整数和浮点字面量,则会根据上下文推断出 Double 类型:

swift 复制代码
let anotherPi = 3 + 0.14159		// 编译器仍然会推断为 Double

7. Numeric Literals 数字字面量

整数字面量可以写成:

  • 十进制数,无前缀
  • 二进制数,前缀为 0b
  • 八进制数,前缀为 0o
  • 十六进制数,前缀为 0x

以下整数字面量都以十进制数 17 结尾:

swift 复制代码
let decimalInteger = 17			  // 17 in 十进制
let binaryInteger = 0b10001       // 17 in 二进制
let octalInteger = 0o21           // 17 in 八进制
let hexadecimalInteger = 0x11     // 17 in 十六进制

对于指数为 x x x 的十进制数,基数乘以 10 x 10^{x} 10x:

  • 1.25e2 表示 1.25 x 10²,即 125.0;
  • 1.25e-2 表示 1.25 x 10⁻²,即 0.0125;

对于指数为 x x x 的十六进制数,基数乘以 2 x 2^{x} 2x:

  • 0xFp2 表示 15 x 2²,即 60.0;
  • 0xFp-2 表示 15 x 2⁻²,即 3.75;

以下所有浮点字面值的十进制值均为 12.1875:

  • 0xFp2 表示 15 x 2², 即60.0;
  • 0xFp-2 表示 15 x 2⁻²,即 3.75;
swift 复制代码
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

数字字面量可以包含额外的格式,以使其更易于阅读。整数和浮点数都可以用额外的零填充,并且可以包含下划线以提高可读性。这两种格式都不会影响字面量的基础值:

swift 复制代码
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

8. Type Conversion 类型转换

8.1 Numeric Type Conversion 数字类型转换

代码中所有通用的整数常量和变量都应使用 Int 类型,在日常情况下使用默认整数类型意味着整数常量和变量可以 立即在代码中互操作,并且将与整数字面值的推断类型匹配。

仅在当前明确需要时才使用其他整数类型,例如来自外部数据大小明确,或者出于性能、内存必要优化的考虑。在这些情况下使用明确的类型有助于捕获意外的值溢出,并隐式地记录所用数据的性质。

8.2 Integer Conversion 整型转换

整数常量或变量可以存储的数字范围因类型而异。Int8 常量或变量可以存储 -128 到 127 之间的数字,而 UInt8 常量或变量可以存储 0 到 255 之间的数字。如果数字无法放入指定大小的整数类型的常量或变量中,则会在代码编译时报错:

swift 复制代码
let cannotBeNegative: UInt8 = -1	// 报错,不能承载负数
let tooBig: Int8 = Int8.max + 1		// 报错,超过了范围

由于每种数字类型可以存储不同范围的值,因此您必须根据具体情况选择是否进行数字类型转换。这种选择方法可以避免隐藏的转换错误,并有助于在代码中明确类型转换意图。

要将一种特定的数字类型转换为另一种,需要使用现有值初始化一个所需类型的新数字。在下面的示例中,常量 twoThousand 的类型为 UInt16,而常量 one 的类型为 UInt8,由于它们不是同一类型,因此不能直接将它们相加。

swift 复制代码
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)

8.3 Integer and Floating-Point Conversion 整型与浮点转换

整数和浮点数类型之间的转换必须明确:

swift 复制代码
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine

浮点数到整数的转换也必须显式进行。整数类型可以用 DoubleFloat 值初始化:

swift 复制代码
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine

let integerPi = Int(pi)

以这种方式初始化新的整数值时,浮点值会被​​截断,如 4.75 会被截断为 4,-3.9 会被截断为 -3。


9. Type Aliases 类型别名

类型别名可以为现有类型定义一个替代名称,使用 typealias 关键字定义类型别名。

swift 复制代码
typealias AudioSample = UInt16

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

swift 复制代码
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min

print(maxAmplitudeFound)    // 0

10. Booleans 布尔值

布尔值被称为逻辑值只能为真或假:truefalse

swift 复制代码
let orangesAreOrange = true
let turnipsAreDelicious = false

orangesAreOrangeturnipsAreDelicious 的类型已被推断为 Bool,与上面的 IntDouble 一样,如果常量或变量在创建时就设置为 true 或 false,则无需将其声明为 Bool。

用 Bool 类型控制条件语句:

swift 复制代码
let turnipsAreDelicious = false

if turnipsAreDelicious {
    print("Mmm, tasty turnips!")
} else {
    print("Eww, turnips are horrible.")
}

Swift 的类型安全机制阻止将非布尔值替换为 Bool 值。以下示例会报告编译时错误:

swift 复制代码
let var = 1
if var{
	// ... 编译报错
}

但是判等条件是可以用的:

swift 复制代码
let var = 1
if var == 1{
	// ... 编译通过
}

11. Tuples 元组

元组将多个值组合成一个复合值。元组中的值可以是任意类型,并且彼此的类型不必相同。

下面的代码中 (404, "Not Found") 是一个描述 HTTP 状态代码的元组,如果您请求的网页不存在,则会返回状态代码 404 Not Found,这个元组将一个 Int 和一个 String 组合在一起。

swift 复制代码
let http404Error = (404, "Not Found")

可以将元组的内容分解为单独的常量或变量并进行访问:

swift 复制代码
let http404Error = (404, "Not Found")

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

可以使用占位符 _ 来使用元组中的一部分:

swift 复制代码
let http404Error = (404, "Not Found")

let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")    // The status code is 404

也可以使用从零开始的索引号访问元组中的各个元素值:

swift 复制代码
let http404Error = (404, "Not Found")

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

定义元组时,可以命名元组中的各个元素,如果为元组中的元素命名,则可以使用元素名称来访问这些元素的值:

swift 复制代码
let http404Error = (statusCode: 200, description: "OK")

print("The status code is \(http404Error.statusCode)")      // The status code is 200
print("The status message is \(http404Error.description)")  // The status message is OK

12. Optionals 可选类型

在值可能缺失的情况下,可以使用可选类型 Optionals。可选类型代表两种可能性:要么存在指定类型的值,要么根本没有值。

以下示例使用初始化器尝试将字符串转换为 Int:

swift 复制代码
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
print(type(of: convertedNumber))        // Optional<Int>

12.1 nil 空值

可以通过为可选变量分配特殊值 nil 来将其设置为无值状态:

swift 复制代码
var serverResponseCode: Int? = 404
serverResponseCode = nil
print(serverResponseCode == nil)        // true

如果定义可选变量而不提供默认值,则该变量将自动设置为 nil:

swift 复制代码
var suveryAnswer: String?
print(suveryAnswer == nil)  // true

可以使用 if 语句通过将可选值与 nil 进行比较来判断其是否包含值。如果可选值有值,则 != nil

swift 复制代码
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

if convertedNumber != nil{
    print("convertedNumber contains some integer value.")
}
// convertedNumber contains some integer value.

不能将 nil 与非可选常量或变量一起使用 。如果代码中的常量或变量需要在某些条件下处理值缺失的情况,应将其声明为相应类型的可选值。声明为非可选值的常量或变量则需要确保永远不会等于 nil 值,否则会引发编译时错误。

这种可选值和非可选值的分离可以 明确标记哪些信息可以缺失 ,并使编写处理缺失值的代码。解包值后,其他使用该值的代码都无需检查其是否为 nil,因此无需在代码的不同部分重复检查相同的值。

访问可选值时,代码始终会同时处理 nil 和非 nil 的情况。当值缺失时,可以执行以下几项操作:

  • 当值为 nil 时,跳过对值进行操作的代码;
  • 通过返回 nil 或使用可选链式调用中描述的 ?. 运算符来传播 nil 值;
  • 使用 ?? 运算符提供回退值;
  • 使用 ! 运算符停止程序执行。

12.2 Optional Binding 可选邦定

可以使用可选绑定来判断可选值是否包含值,如果包含则将该值用作临时常量或变量。可选绑定可以与 ifguardwhile 语句一起使用,用于检查可选值中的值,并将该值提取到常量或变量中,作为单个操作的一部分。

if 语句编写一个可选绑定,下面的代码意思为:如果 Int(possibleNumber) 返回的可选 Int 类型包含值,则将名为 actualNumber 的新常量设置为该可选类型包含的值。

swift 复制代码
let possibleNumber = "123"

if let actualNumber = Int(possibleNumber){
    print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
}else{
    print("The string \"\(possibleNumber)\" couldn't be converted to an integer")
}
// The string "123" has an integer value of 123

如果在访问其包含的值后不需要引用原始的可选常量或变量,则可以对 新的常量或变量使用相同的名称 ,这段代码首先检查 myNumber 是否包含值,如果 myNumber 有值,则将名为 myNumber 的新常量的值设置为该值。

swift 复制代码
let possibleNumber = "123"
let myNumber = Int(possibleNumber)

if let myNumber = myNumber{
    print("My number is \(myNumber)")
}
// My number is 123

可以将常量和变量与可选绑定一起使用。如果在 if 语句的第一个分支中操作 myNumber 的值,可以改写为 if var myNumber ,这样可选值中包含的值将作为变量而不是常量使用。在 if 语句主体中对 myNumber 所做的更改仅适用于该局部变量,而不会影响解包的原始可选常量或变量。

在单个 if 语句中包含 任意数量 的可选绑定和布尔条件并以逗号分隔。如果可选绑定中的任何值为 nil 或任何布尔条件的计算结果为 false,则整个 if 语句的条件将被视为 false:

swift 复制代码
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100{
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 4 < 42 < 100

上面的代码等价于下面的代码:

swift 复制代码
if let firstNumber = Int("4"){
    if let secondNumber = Int("42"){
        if firstNumber < secondNumber && secondNumber < 100{
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// 4 < 42 < 100

12.3 Providing a Fallback Value 提供备选值

处理缺失值的另一种方法是使用空值合并运算符 ?? 提供默认值。如果 ?? 左侧的可选项不为 nil,则解包并使用该值;否则使用 ?? 右侧的值。下面的代码会在指定姓名的情况下使用姓名问候某人,当姓名为 nil 时,使用通用问候语。

swift 复制代码
var name: String? = nil
let greeting = "Hello, " + (name ?? "friend") + "!"

print(greeting) // Hello, friend!

13. Unwrapping 解包

13.1 Force Unwrapping 强制解包

nil 表示不可恢复的故障时,可以通过在可选项名称末尾添加感叹号 ! 来访问底层值,称为强制解包可选项的值。强制解包非 nil 值时,返回其解包后的值;强制解包 nil 值会触发运行时错误。

! 实际上是 fatalError(_:file:line:) 的缩写,以下代码展示了两种等效的方法:

swift 复制代码
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

let number = convertedNumber!

guard let number = convertedNumber else{
    fatalError("The number was invalid")
}

13.2 Implicitly Unwrapped Optionals 隐式解包可选值

可选值表示常量或变量可以"无值"。可以使用 if 语句检查可选值是否存在,如果存在,可以使用可选绑定进行条件解包以访问可选值。可选值在首次设置后始终会有一个值,在这种情况下无需在每次访问可选值时都检查并解包它,因为可以安全地假定始终有值。

这类可选值被定义为 隐式解包可选值 ,编写隐式解包可选值时需要在可选值的类型后放置感叹号 String! 而不是问号 String?。使用可选值时,应在声明可选值时在其类型后放置 !,而不是在其名称后放置 !

Swift 中隐式解包可选项的主要用途是在 类初始化期间 。如果变量以后有可能变为 nil,请勿使用隐式解包可选类型。如果需要在变量的生命周期内检查其是否为 nil 值,应始终使用普通可选类型。

隐式解包可选类型在后台与普通可选类型相同,但也可以像非可选值一样使用,无需在每次访问时都解包可选值。以下示例展示了在以显式字符串形式访问可选字符串和隐式解包可选字符串的包装值时,两者的行为差异:

swift 复制代码
let possibleString: String? = "An optional string"
let forcedString: String = possibleString!      // 显示解包

let assumedString: String! = "An implicitly unwrapped optional string"
let implicitString: String = assumedString      // 隐式解包

可以将隐式解包的可选值理解为 允许在需要时强制解包该可选值 。当使用隐式解包的可选值时,Swift 首先会尝试将其用作普通的可选值;如果不能被用作可选值,Swift 会强制解包该值。在上面的代码中,可选值 supposedString 在赋值给 implicitString 之前被强制解包,因为 implicitString 具有显式的非可选类型 String

在下面的代码中,optionalString 没有显式类型,因此它是一个普通的可选值:

swift 复制代码
let assumedString: String! = "An implicitly unwrapped optional string"

let optionalString = assumedString

如果隐式解包的可选类型为 nil,当尝试访问其包装值会触发 runtime error。其结果与使用 ! 强制解包一个不包含值的普通可选类型完全相同。可以像检查普通可选类型一样检查隐式解包的可选类型是否为 nil

swift 复制代码
let assumedString: String! = "An implicitly unwrapped optional string"

if assumedString != nil{
    print(assumedString!)
}
// An implicitly unwrapped optional string

还可以使用带有可选绑定的隐式解包可选值,在单个语句中检查并解包其值:

swift 复制代码
let assumedString: String! = "An implicitly unwrapped optional string"

if let definiteString = assumedString{
    print(definiteString)
}
// An implicitly unwrapped optional string

14. Memory Safety 内存安全

除了上面类型安全和类型推断中描述的防止类型不匹配的检查之外,Swift 还保护代码免受无效内存的影响。这种保护称为内存安全,并包含以下要求:

  • 值在读取之前设置。防止与未初始化内存区域交互的保护也称为明确初始化。
  • 数组和缓冲区只能在有效索引处访问。防止越界访问的保护也称为边界安全。
  • 内存只能在值的生命周期内访问。防止释放后使用错误的保护也称为生命周期安全。
  • 内存访问仅以可证明安全的方式重叠。防止并发代码中可能出现的数据竞争的保护也称为线程安全。

如果你过去使用过类型不安全语言(如C++),可能熟悉上面列出的一些错误和 bug。Swift 中的安全代码可以避免这些问题。

有时需要在安全范围之外做些工作,例如由于语言或标准库的限制,Swift 也提供了某些 API 的非安全版本。当使用名称中包含"不安全"、"未检查"或"非托管"等字眼的类型或方法时,需要自己承担安全责任。

尽管 Swift 中的安全代码,但仍然可能遇到错误和意外故障导致程序停止执行。Swift 提供了几种指示和恢复错误的方法,但在某些情况下,处理错误的唯一安全方法是停止执行。如果需要保证服务永远不会意外停止,请在其整体架构中加入容错功能,以便它可以从任何组件的意外停止中恢复。


15. Error Handling 异常处理

可以使用错误处理来响应程序在执行过程中可能遇到的错误情况。

与可选值不同,错误处理用来定位失败的根本原因,并在必要时将错误传播到程序的其他部分。当函数遇到错误情况时会抛出一个错误。该函数的调用者可以捕获该错误并做出适当的响应。

swift 复制代码
func canThrowAnError() throws{
    // 在函数中可能抛出错误
}

函数声明中包含 throws 关键字表示可以抛出错误。调用一个可以抛出错误的函数时,需要在表达式前面添加 try 关键字。Swift 会自动将错误传递到当前作用域之外,直到被 catch 子句处理。

swift 复制代码
func canThrowAnError() throws{
    // 在函数中可能抛出错误
}

do {
    try canThrowAnError()
    // 没有异常抛出时运行到这个位置
}catch{
    // 抛出异常后跳转到这里
}

do 语句会创建一个新的包含作用域,允许将错误传播到一个或多个 catch 子句。以下示例展示了如何使用错误处理来响应不同的错误条件:

swift 复制代码
enum SandwichError: Error{
    case outOfCleanDishes
    case missingIngredients(ingredients:Int)
}

func makeASandwich() throws{
    // ...
}

func eatASandwich(){
    // ...
}

func washDishes(){
    // ...
}

func buyGroceries(ingredients: Int){
    // ...
}

do {
    try makeASandwich()
    eatASandwich()
}catch SandwichError.outOfCleanDishes {
    washDishes()
}catch SandwichError.missingIngredients (let ingredients){
    buyGroceries(ingredients: ingredients)
}

16. Assertions and Preconditions 断言与先决条件

AssertionsPreconditions 是在运行时进行的检查,可以使用它们来确保在执行任何后续代码之前满足基本条件。如果断言或先决条件中的布尔条件为真,则代码将继续照常执行;如果条件为假,则程序的当前状态无效。

可以使用断言和先决条件来表达在编码时所做的假设和期望,断言可以在开发过程中发现错误和不正确的假设先决条件可以在生产环境中检测问题。

除了在运行时验证期望之外,断言和先决条件也是代码中一种有用的文档形式。与上面"错误处理"中讨论的错误条件不同,断言和先决条件不用于可恢复或预期的错误。由于失败的断言或先决条件表示程序状态无效,因此无法捕获失败的断言。当断言失败时,程序中至少有一部分数据无效,但并无法知道无效的原因,也不知道其他状态是否也无效。

使用断言和前提条件并不能替代以不太可能出现无效条件的方式设计代码,但使用它们来强制执行有效的数据和状态,可以用在出现无效状态时进行可预测地终止,并有助于更轻松地调试问题。如果不检查假设,可能要等到很久以后当其他代码部分开始明显出现故障,并且用户数据已悄无声息地损坏时,才会注意到此类问题。一旦检测到无效状态,立即停止执行也有助于限制该无效状态造成的损害。

断言和先决条件的区别在于检查的时间:断言仅在调试版本中检查而先决条件在调试版本和生产版本中都会检查。在生产版本中,断言中的条件不会被评估,可以在开发过程中使用任意数量的断言,而不会影响生产性能。

16.1 Debugging with Assertions 使用断言Debug

可以通过调用 Swift 标准库中的 assert(_:_:file:line:) 函数来编写断言,需要向此函数传递一个计算结果为 true 或 false 的表达式,以及一条在条件结果为 false 时显示的消息。例如:

swift 复制代码
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// 触发断言,因为条件不满足

可以省略断言消息:

swift 复制代码
let age = -3
assert(age >= 0)

如果代码已经检查了条件,则可以使用 assertionFailure() 函数来直接触发断言失败:

swift 复制代码
let age = -3

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.")
}

16.2 Enforcing Preconditions 执行先决条件

当条件有可能为假,但必须为真才能使代码继续执行时,可以使用 precondition,如使用前提条件检查下标是否超出范围,或检查函数是否已传递有效值。

可以通过调用 precondition() 函数来编写前提条件,需要向此函数传递一个计算结果为真或假的表达式,以及一条在条件结果为假时显示的消息。例如:

swift 复制代码
let index = 0
precondition(index > 0, "Index muet be greater than zero")

可以调用 preconditionFailure() 函数来指示发生了失败,例如 switch 的默认情况,但所有有效输入数据都应该由 switch 的其他情况之一处理。

相关推荐
m0_7198171122 分钟前
Linux运维新人自用笔记(配置本地光盘yum镜像源、离线安装rpm包、yum缓存rpm包、安装指定版本软件、查询依赖关系、docker简单部署)
linux·学习
NULL指向我1 小时前
C语言数据结构笔记5:Keil 编译器优化行为_malloc指针内存分配问题
c语言·数据结构·笔记
hello kitty w2 小时前
Python学习(9) ----- Python的Flask
python·学习·flask
Chef_Chen3 小时前
从0开始学习R语言--Day22--km曲线
学习
委婉待续3 小时前
Qt的学习(三)
开发语言·qt·学习
wusixuan1310044 小时前
最大闭合子图学习笔记 / P2805 [NOI2009] 植物大战僵尸
笔记·学习·算法·最大闭合子图
moxiaoran57534 小时前
uni-app项目实战笔记5--使用grid进行定位布局
笔记·uni-app
羊小猪~~4 小时前
数据库学习笔记(十五)--变量与定义条件与处理程序
数据库·人工智能·笔记·后端·sql·学习·mysql
梦境虽美,却不长4 小时前
数据结构 线性表 学习 2025/6/12 21点27分
数据结构·学习
霸王蟹5 小时前
带你手写React中的useReducer函数。(底层实现)
前端·javascript·笔记·学习·react.js·typescript·前端框架