Swift5.x-基础(中文文档)

2,366 阅读34分钟

引言

继续学习Swift文档,从上一章节:开篇 ,我们了解Swift基本的知识点,现在我们还是从详细的基础知识开始,不要认为基础知识不重要,这是掌握一门开发语言的基本。由于篇幅较长,这里分篇来记录,接下来,开始吧!

如果你已经掌握了Swift的基础,那么请参阅下一章节:基本操作

基础

Swift是一门新的针对iOS、macOS、watchOS和tvOS开发的编程语言。尽管如此,根据您使用C和Objective-C进行开发的经验,您将熟悉Swift的许多部分。

Swift提供了自己版本的所有基本C和Objective-C类型,包括Int对应整型,Double和Float对应浮点类型,Bool对应布尔类型,和String对应文本数据。Swift还提供了三种主要收藏类型的强大版本,Array,Set和Dictionary,定义在 Collection Types.

像C一样,Swift使用变量来存储和通过一个标识名称引用值。Swift还大量使用了不能更改值的变量。这些被称为常量,比c中的常量功能强大得多。当你处理不需要更改的值时,在Swift中使用常量使代码更安全、更清晰。

除了熟悉的类型,Swift还引入了Objective-C中没有的高级类型,比如元组。元组使您能够创建和传递值分组。可以使用元组将函数中的多个值作为一个复合值返回。

Swift还引入了可选类型,用于处理缺少值的情况。可选值要么说“有一个值,它等于x”,要么说“根本没有值”。使用可选值类似于在Objective-C中对指针使用nil,但它们适用于任何类型,而不仅仅是类。option不仅比Objective-C中的nil指针更安全、更有表现力,而且是Swift许多最强大功能的核心。

Swift是一种类型安全的语言,这意味着该语言可以帮助您清楚地了解代码可以使用的值的类型。如果您的部分代码需要String,类型安全防止您错误地将其传递为Int。同样,类型安全可以防止您意外地将可选String传递给需要非可选String的代码段。类型安全可以帮助您尽早捕获和修复开发过程中的错误。

1 常量和变量

常量和变量将名称(如maximumnumberoflogintries或welcomeMessage)与特定类型的值(如数字10或字符串“Hello”)关联起来。常量的值一旦设置就不能更改,而变量可以在将来设置为不同的值。

1.1 定义常量和变量

常量额变量在使用前必须声明。可以用let关键字声明常量,用var关键字声明变量。下面是一个如何使用常量和变量来跟踪用户尝试登录的次数的例子:

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

这段代码意思是: “定义一个maximumNumberOfLoginAttempts常量,并赋值为10。定义一个currentLoginAttempt变量,并赋值为0.”

在这个例子中,声明了一个最大允许登录次数的常量,因为这个最大值没有被修改。当前登录尝试计数器被声明为一个变量,因为在每次登录尝试失败后,该值必须递增。

你可以在一行里声明多个常量和多个变量,用逗号分隔:

var x = 0.0, y = 0.0, z = 0.0

注意 如果代码中存储的值不会改变,请始终使用let关键字将其声明为常量。变量只用于存储需要更改的值。

1.2 类型注解

当你声明常量和变量时,可以添加类型,这样就可以知道需要存储的值的类型。通过在常量或变量名后面加上冒号、空格和要使用的类型名来编写类型注释。

这个示例为一个名为welcomeMessage的变量提供了一个类型注释,以表明该变量可以存储字符串值:

var welcomeMessage: String

声明中的冒号表示“…of type…”,因此上面的代码可以理解为:

“定义一个名称叫welcomeMessage的字符串变量。”

短语“of type String”意味着“可以存储任何字符串值”。把它看作是可以存储的“事物的类型”(或“事物的种类”)的意思。

welcomeMessage变量可以设置为任何字符串值:

welcomeMessage = "Hello"

你可以在一行中定义多个相同类型的相关变量,用逗号分隔,最后的变量名后面有一个类型注释:

var red, green, blue: Double

实际上,很少需要编写类型注释。如果在定义常量或变量时提供初始值,Swift几乎总是可以推断该常量或变量使用的类型,如 类型安全和类型推断 所述。在上面的welcomeMessage示例中,没有提供初始值,因此welcomeMessage变量的类型是通过类型注释指定的,而不是从初始值推断出来的。

1.3 命名常量和变量

常量和变量的名称支持几乎所有字符,包括Unicode字符:

let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"

常量个变量的名称不能包含空格数学符号箭头私有Unicode标量值,或**-box-drawing**字符。也不能以数字开头,尽管数字可以包含在名称的其他地方。

一旦声明了一个某些类型的常量和变量,就不能声明和它相同名称的,或者改变它存储的类型。尽管可以将一个常量修改为变量或者一个变量修改为常量。

注意 如果您需要为一个常量或变量提供与保留的Swift关键字相同的名称,在使用该关键字作为名称时使用反引号(`)。但是,除非别无选择,否则要避免使用关键字作为名称。

可以将现有变量的值更改为另一个兼容类型的值,例如:

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome is now "Bonjour!"

和变量不同的是,常量一旦设置了值,就不能再修改,如果强行修改会报错:

let languageName = "Swift"
languageName = "Swift++"
// This is a compile-time error: languageName cannot be changed.

1.4 打印常量和变量

可以通过**print(_:separator:terminator:)**函数打印常量或变量:

print(friendlyWelcome)
// Prints "Bonjour!"

**print(:separator:terminator:)**函数是一个全局函数,它将在适当的输出中打印一个或多个值。在Xcode中,例如,**print(:separator:terminator:)**函数会在Xcode的控制台上输出打印的值。 separator和 terminator有默认值,因此当调用这个函数时可以忽略它们。默认情况下,函数通过添加换行符来终止它打印的行。若要打印值后不带换行符,请传递一个空字符串作为终止符--例如: print(someValue, terminator: "")。有关具有默认值的参数的信息,参阅 Default Parameter Values

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

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

注意 字符串插值可以使用的所有选项在String Interpolation都有描述。

2 注释

使用注释将不可执行文本包含在代码中,作为对自己的提示或提醒。代码编译时,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. */

嵌套多行注释使您能够快速而轻松地注释掉大块代码,即使代码已经包含多行注释。

3 分号

和许多其他语言不同,Swift不需要在语句末尾写分号,尽管你希望这样做。然而,当将多个语句写在同一行时,需要加分号分隔:

let cat = "🐱"; print(cat)
// Prints "🐱"

4 整型

整数是没有分数成分的整数,例如42和-23。整数可以是有符号的(正、零或负)或无符号的(正或零)。

Swift提供8位、16位、32位和64位格式的有符号整数和无符号整数。这些整数遵循类似于C的命名约定,即8位无符号整数的类型为UInt8, 32位有符号整数的类型为Int32。与Swift中的所有类型一样,这些整数类型的名称都是大写的。

4.1 整数范围

您可以访问每个整数类型的最小值和最大值及其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),因此可以在表达式中与其他相同类型的值一起使用。

4.2 Int

在大多数情况下,您不需要在代码中选择特定大小的整数。Swift提供了一个额外的整数类型Int,它与当前平台的本地字大小相同:

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

除非你需要处理一个特定大小的整数,否则整数都要用Int定义。这有助于代码的一致性和互操作性。即使在32位平台上,Int可以存储的值在-2,147,483,648和2,147,483,647之间,这个范围可以用在大多数整数上了。

4.3 UInt

Swift也提供了无符号的整型,UInt,它与当前平台的本地字大小相同:

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

注意 仅当您特别需要与平台的本地字大小相同的无符号整数类型时,才使用UInt。如果不是这样,则首选Int,即使已知要存储的值是非负的。整数值一致地使用Int有助于代码的互操作性,避免不同数字类型之间的转换,并匹配整型类型推断,如类型安全和类型推断中所述。

5 Floating-浮点数

浮点数是由小数部分组成的数,如3.14159、0.1和-273.15。

与整型相比,浮点型可以代表更大范围的值,并且可以存储比整型更大或更小的数字。Swift提供了两种带符号的浮点数类型:

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

注意 Double的精度至少为15位十进制数字,而Float的精度只有6位十进制数字。要使用的适当浮点类型取决于代码中需要使用的值的性质和范围。如果两种类型都合适,则首选Double。

6 类型安全和类型推断

Swift是一种类型安全的语言,类型安全语言鼓励您明确代码可以使用的值的类型。如果代码的一部分需要字符串,则不能错误地将其传递为Int。

因为Swift是类型安全的,它在编译代码时执行类型检查,并将任何不匹配的类型标记为错误。这使您能够在开发过程中尽早捕获和修复错误。

当您使用不同类型的值时,类型检查可以帮助您避免错误。但是,这并不意味着必须指定声明的每个常量和变量的类型。如果没有指定所需值的类型,Swift会使用类型推断来计算出适当的类型。类型推断使编译器在编译代码时能够通过检查提供的值自动推断特定表达式的类型。

由于有了类型推断,Swift需要的类型声明要比C或Objective-C等语言少得多。常量和变量仍然是显式类型的,但是指定它们的类型的大部分工作已经为您完成了。

在声明具有初始值的常量或变量时,类型推断特别有用。这通常是通过在声明常量或变量时为其分配一个文字值(或文字)来实现的。(文字值是直接出现在源代码中的值,例如下面示例中的42和3.14159。)

例如,如果你给一个新常量赋值为42而没有说明它是什么类型,Swift推断你希望这个常量是一个整型数,因为你用一个看起来像整数的数字初始化了它:

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)。

如果你在一个表达式中结合了整型和浮点型文字,Double类型会从上下文推断出来:

let anotherPi = 3 + 0.14159
// anotherPi is also inferred to be of type Double

字面值3本身没有显式的类型,因此可以从作为加法的一部分的浮点字面值中推断出适当的输出类型Double。

7 数字字面值

整型文字可以写成:

  • 无前缀的十进制数
  • 一种二进制数,前缀为0b
  • 带有0o前缀的八进制数
  • 带有0x前缀的十六进制数字

所有这些字面值都有一个十进制值17:

let decimalInteger = 17
let binaryInteger = 0b10001       // 17  二进制
let octalInteger = 0o21           // 17  八进制
let hexadecimalInteger = 0x11     // 17  十六进制

浮点字面值可以是十进制(没有前缀)或十六进制(有0x前缀)。它们必须在小数点的两边都有一个数字(或十六进制数)。十进制浮点数还可以有一个可选的指数,由大写或小写e表示;十六进制浮点数必须有一个指数,用大写或小写p表示。

对于指数为exp的小数,底数乘以10exp:

  • 1.25e2 意味着 1.25 x 102, 或 125.0.
  • 1.25e-2 意味着 1.25 x 10-2, 或 0.0125.

对于指数为exp的十六进制数,底数乘以2exp:

  • 0xFp2 意味着 15 x 22, 或 60.0.
  • 0xFp-2 意味着 15 x 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

8 数值类型转换

对代码中的所有通用整型常量和变量使用Int类型,即使已知它们是非负的。在日常情况中使用默认整数类型意味着整型常量和变量在代码中立即可以互操作,并将匹配整型文字值的推断类型。

只有在手头的任务特别需要其他整数类型时才使用它们,因为外部源显式地调整了数据大小,或者为了性能、内存使用或其他必要的优化。在这些情况下使用显式大小的类型有助于捕获任何意外值溢出,并隐式记录所使用数据的性质。

8.1 整数转换

可以存储在整型常量或变量中的数字的范围对于每种数值类型是不同的。Int8常量或变量可以存储-128到127之间的数字,而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

由于每个数值类型可以存储不同范围的值,因此必须根据具体情况选择进行数值类型转换。这种可选择的方法可以防止隐藏的转换错误,并有助于在代码中显式地显示类型转换意图。

要将一种特定的数字类型转换为另一种,可以使用现有值初始化所需类型的新数字。在下面的示例中,常数2000的类型为UInt16,而常数1的类型为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提供初始化器的类型。扩展包括扩展现有类型以提供接受新类型(包括您自己的类型定义)的初始化器。

8.2 整数和浮点数转换

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

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double

在这里,常量3的值用于创建一个类型为Double的新值,这样加法的两边都是相同的类型。如果不进行此转换,则不允许添加。

浮点到整数的转换也必须明确。整数类型可以用双值或浮点值初始化:

let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type Int

以这种方式初始化新的整数值时,浮点值总是被截断。这意味着4.75变成4,而-3.9变成-3。

注意 组合数值常量和变量的规则与组合数值文字的规则不同。文字值3可以直接添加到文字值0.14159,因为数字文字本身没有显式的in和of类型。它们的类型只有在由编译器计算时才会推断出来。

9 类型别名

类型别名定义现有类型的替代名称。可以通过typealias关键词定义类型别名。

当你想通过上下文更合适的名称来引用一个现有的类型时,类型别名很有用,例如当从外部来源处理特定大小的数据时:

typealias AudioSample = UInt16

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

var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0

这里,AudioSample被定义为UInt16的别名。因为它是一个别名,调用AudioSample.min也就是调用UInt16.min,作为maxAmplitudeFound为0的值。

10 布尔值

Swift有一个基本的布尔类型,叫做Bool。布尔值被称为逻辑值,因为它们只能是真或假。Swift提供了两个布尔常量值true和false:

let orangesAreOrange = true
let turnipsAreDelicious = false

orangesAreOrange和turnipsAreDelicious的类型已经被推断为Bool,因为它们是用布尔文字值初始化的。与上面的Int和Double一样,如果在创建常量或变量时就将它们设置为true或false,则不需要将它们声明为Bool。当Swift用其他已知类型的值初始化常量或变量时,类型推断有助于使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中的其他类型安全示例一样,这种方法避免了意外错误,并确保特定代码段的意图始终清晰。

11 元组

元组将多个值分组为一个复合值。元组中的值可以是任何类型,不必彼此具有相同的类型。

在本例中,(404,“Not Found”)是描述HTTP状态代码的元组。HTTP状态码是web服务器在请求web页面时返回的特殊值。如果你请求的网页不存在,它会返回404 Not Found状态码。

let http404Error = (404, "Not Found")
// http404Error is of type (Int, String), and equals (404, "Not Found")

元组将一个Int和一个字符串组合在一起,为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"

元组作为函数的返回值特别有用。试图检索web页面的函数可能会返回(Int, String)元组类型,以描述页面检索的成功或失败。通过返回具有两个不同类型值的元组,与只能返回单个类型的单个值相比,函数提供了关于其结果的更有用的信息。有关更多信息,请参见具有多个返回值的函数

注意 元组对于相关值的简单组非常有用。它们不适合创建复杂的数据结构。如果数据结构可能更复杂,那么将其建模为类或结构体,而不是元组。有关更多信息,请参见结构体和类

12 可选值

在可能没有值的情况下使用可选值。可选的表示两种可能:要么有一个值,您可以展开可选的来访问该值,要么根本没有值。

注意 可选值的概念在C或Objective-C中不存在。Objective-C中最接近的是,从方法中返回nil的能力,否则会返回一个对象,nil表示“缺少一个有效对象”。但是,这只适用于对象,而不适用于结构体、基本C类型或枚举值。对于这些类型,Objective-C方法通常返回一个特殊的值(比如NSNotFound)来表示没有值。这种假设方法的调用者知道有一个特殊的值要进行测试,并且记得检查它。Swift的可选值可以让你指示任何类型的值是否存在,而不需要特殊的常量。

下面是一个示例,说明如何使用可选值来处理缺少值的情况。Swift的Int类型有一个初始化器,它试图将字符串值转换为Int值。但是,不是每个字符串都可以转换为整数。字符串“123”可以转换为数字值123,但是字符串“hello, world”没有明显的数字值可以转换。

下面的例子使用了初始化器来尝试将一个字符串转换成Int类型:

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"

因为初始化器可能失败,它返回一个可选的整型,而不是整型。问号表示它包含的值是可选的,这意味着它可能包含一些Int值,也可能根本不包含值。它不能包含任何其他东西,比如Bool值或String值。要么是整数,要么什么都不是)

12.1 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里的不同,OC里的nil是一个指针,指向一个不存在的对象。Swift里的nil不是一个指针--它是某种类型的值的缺失。任何类型的选项都可以设置为nil,而不仅仅是对象类型。

12.2 If语句和强制解包

通过比较可选值和空值,可以使用if语句来确定可选值是否包含值。您可以使用“等于”操作符(==)或“不等于”操作符(!=)执行比较。

如果可选值有值,会被认为不是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值!强制打开它的值。

12.3 可选值绑定

您可以使用可选绑定来查明可选项是否包含值,如果包含值,则使该值作为临时常量或变量可用。可选绑定可以与if和while语句一起使用,以检查可选语句中的值,并将该值提取为常量或变量,这是单个操作的一部分。if和while语句在控制流中有更详细的描述。

使用If语句写一个如下的可选绑定:

if let constantName = someOptional {
    statements
}

您可以重写Optionals部分中的possibleNumber示例,以使用可选绑定而不是强制解包:

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

这段代码的意思是: “如果Int(possibleNumber)返回的可选整数包含一个值,则设置一个名为actualNumber的新常量为可选整数中包含的值。”

如果转换成功,就可以在If语句的第一个分支中使用actualNumber常数。它已经用可选项中包含的值进行了初始化,因此不需要使用!后缀来访问其值。在本例中,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")
}
// 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语句后面的代码行中使用,如Early Exit中所述。

12.4 隐式解包可选值

如上所述,可选值表示常量或变量允许“无值”。可选值可以用if语句检查值是否存在,也可以用可选绑定有条件地解除包装,以访问可选值(如果存在)。

有时候从程序结构上,可选值在第一次设置后,总会有一个值。在这些情况下,在访问可选值的时候,移除检查和解包的操作是有效的,因为它总是安全地假定有一个值。

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

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation point

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for an exclamation point

(注:这里终于清楚地明白了?和!的用法,说明学一门语言还是要从官方文档开始)

你可以将可选的隐式解包看作允许可选的对象在需要时强制解包。当你使用隐式解包装可选值时,Swift首先尝试将其作为普通可选值使用;如果不能作为可选值使用,Swift强制打开该值。在上面的代码中,可选值assumedString在将其值分配给implicitString之前被强制解除包装,因为implicitString具有显式的、非可选的字符串类型。在下面的代码中,optionalString没有显式类型,所以它是一个普通的可选类型。

let optionalString = assumedString
// The type of optionalString is "String?" and assumedString isn't force-unwrapped.

如果一个可选的隐式解包是nil,并且您尝试访问它的包装值,您将触发一个运行时错误。其结果与在不包含值的普通可选项后放置感叹号完全相同。

你可以检查一个可选的隐式解包是否为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值,请始终使用普通的可选类型。

13 错误处理

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

可选值可以使用值的存在或不存在来传递函数的成功或失败,与之相反,错误处理允许您确定失败的根本原因,并在必要时将错误传播到程序的另一部分。

当函数遇到错误条件时,它会抛出错误。然后,函数的调用者可以捕获错误并作出适当的响应。

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匹配。missingcomponents情况下,使用catch模式捕获的相关[String]值调用buyGroceries(_:)函数。

错误处理中更详细地介绍了抛出、捕获和传播错误。

14 断言和先决条件

断言和前置条件是在运行时进行的检查。在执行任何其他代码之前,使用它们确保满足必要条件。如果断言或先决条件中的布尔值为真,则代码执行照常继续。如果条件计算为false,则程序的当前状态无效;代码执行结束,应用程序终止。

在编码时,可以使用断言和先决条件来表示所作的假设和期望,因此可以将它们作为代码的一部分。断言帮助您在开发期间发现错误和不正确的假设,而前提条件帮助您检测生产中的问题。

除了在运行时验证您的期望之外,断言和先决条件也成为代码中有用的文档形式。与上面错误处理中讨论的错误条件不同,断言和前置条件不会用于可恢复的或预期的错误。由于失败的断言或先决条件指示无效的程序状态,因此无法捕获失败的断言。

使用断言和先决条件不能替代以不太可能出现无效条件的方式设计代码。但是,使用它们强制执行有效的数据和状态会导致应用程序在出现无效状态时可预测地终止,并有助于使问题更容易调试。在检测到无效状态时立即停止执行还有助于限制由该无效状态造成的损害。

断言和前提条件的区别在于它们被检查的时候:断言只在调试构建中被检查,但是前提条件在调试和生产构建中都被检查。在生产构建中,不计算断言中的条件。这意味着您可以在开发过程中使用任意数量的断言,而不会影响生产中的性能。

14.1 用断言调试

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

let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// This assertion fails because -3 is not >= 0.

在本例中,如果age >= 0的值为真(即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.")
}

14.2 执行先决条件

只要条件可能为假,但必须为真,代码才能继续执行,就使用前置条件。例如,使用先决条件检查下标是否超出范围,或检查函数是否传递了有效值。

你可以通过调用 precondition(_:_:file:line:)函数来编写一个先决条件。向该函数传递一个计算结果为true或false的表达式,以及一条消息,如果条件的结果为false,则显示该消息。例如:

// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")

您还可以调用preconditionFailure(_:file:line:)函数来指示发生了故障——例如,如果采用了switch的默认情况,但是所有有效的输入数据应该由switch的其他情况之一处理。

注意 如果以unchecked模式(-Ounchecked)编译,则不会检查前置条件。编译器假设前提条件总是为真,并相应地优化代码。然而,无论优化设置如何,fatalError(_:file:line:)函数总是会停止执行。

您可以在原型和早期开发期间使用fatalError(_:file:line:)函数为尚未实现的功能创建存根,方法是编写fatalError(“未实现”)作为存根实现。因为与断言或先决条件不同,致命错误从来没有被优化出来,所以可以肯定,如果遇到存根实现,执行总是会停止。

总结

通过这一章节的学习,可以了解Swift基础的一些知识以及它特有的一些功能:

  • 如何去定义常量和变量
  • 如何添加注释:和OC差不多,不过比OC多了个**"""**注释多行的符号
  • 分号的作用:单行上写多行代码时需要加上
  • 整型、浮点型和布尔值
  • Swift特有的类型安全和类型推断:这个真的很实用,可提高开发效率
  • 数字字面值和数值类型转换
  • 类型别名
  • 元组
  • 可选值:?和!的用法,以及可选绑定if-let的用法
  • 错误处理:try-catch和do-try-catch
  • 断言和先决条件 这里就不详细说明各个知识点了,可以从上面的内容中寻找答案。最后,如果大家喜欢的话,可以给个star哦,有你的支持,就是我的动力!

上一章节:开篇
下一章节:基本操作

参考文档:Swift - The Basics