Swift是一种用于iOS、macOS、watchOS和tvOS应用程序开发的新编程语言。尽管如此,从您使用C和Objective-C开发的经验来看,Swift的许多部分都是熟悉的。
Swift提供了所有基本C和Objective-C类型的自己版本,包括整数的Int、浮点值的Double和Float、布尔值的Bool和文本数据的String。Swift还提供了三个主要collection类型(array、set和dictionary)的强大版本,如collection类型中所述。
与C语言一样,Swift使用变量存储值,并通过标识名称引用值。Swift还广泛使用了那些值无法更改的变量。这些变量被称为常量,比c中的常量功能强大得多。当您处理不需要更改的值时,常量在Swift中被用于使代码更安全、意图更清晰。
除了熟悉的类型,Swift还引入了Objective-C中没有的高级类型,比如元组。元组使您能够创建和传递值分组。您可以使用元组作为单个复合值从函数返回多个值。
Swfit 也介绍了可选类型 optional, 用来处理没有值得情况。
optional 表明有一个值且为x或者一个值也没有。
使用 optional 类似于OC中的nil指针,但是可选类型不止用于类,它适用于任何类型。而且安全性更强,更具有表现力,是Swift很多强大功能的核心。
Swift是一种类型安全的语言,这意味着该语言可以帮助您明确代码可以使用的值的类型。如果代码的一部分需要字符串,类型安全性可以防止您错误地传递Int。同样,类型安全性可以防止您意外地将可选字符串传递给需要非可选字符串的代码段。类型安全帮助您在开发过程中尽早捕获和修复错误。
Constants and Variables 常量和变量
常量和变量将名称(如maximumnumberoflogintry或welcome emessage)与特定类型的值(如数字10或字符串“Hello”)关联起来。常量的值一旦设置好就不能更改,而变量可以在将来设置为不同的值。
Declaring Constants and Variables 声明常量和变量
常量和变量必须在使用之前就进行声明。
使用关键字 let 关键字声明常量,使用 var 关键字声明变量
下面是一个如何使用常量和变量来跟踪用户尝试登录的次数的例子:
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
此代码可读作:
声明一个名为maximumNumberOfLoginAttempts的常量,并将其值设为10,然后又声明一个名为currentLoginAttempt的变量,并给它一个初始值0。”
在本例中,允许的最大登录尝试数被声明为常量,因为最大值从未更改。当前登录尝试计数器被声明为一个变量,因为这个值必须在每次登录尝试失败后递增。
你也可以在一行中声明许多常量或变量,使用 逗号“,”分开即可
var x = 0.0, y = 0.0, z = 0.0
注意:如果存储值不会被改变,总是用 let 关键字声明为常量,存储值会改变则必须使用 var 关键字声明为变量
Type Annotations 类型注解
当你在声明常量或变量需要清楚地表明其类型时,可以使用类型注解。只需要在声明变量或者常量时,在名字后面添加冒号“:”和类型名即可。
这个例子为一个名为welcomeMessage的变量提供了一个类型注释,以表明该变量可以存储字符串值:
var welcomeMessage: String
您可以在一行中定义相同类型的多个相关变量,中间用逗号分隔,最后的变量名后面有一个类型注释:
var red, green, blue: Double
在实践中很少需要编写类型注释。如果在定义常量或变量时为其提供一个初值,Swift几乎总是可以推断出该常量或变量要使用的类型,如 Type Safety and Type Inference 中所述。在上面的welcomeMessage示例中,没有提供初值,因此使用类型注释指定了welcomeMessage变量的类型,而不是从初值推断。
Naming Constants and Variables 命名常量和变量
常量和变量的命名几乎可以包含任何字符,包括Unicode字符:
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
常量和变量名不能包含空格字符、数学符号、箭头、私有使用的Unicode标量值或行和箱形图字符。它们也不能以数字开头,尽管数字可能包含在名称的其他地方。
一旦声明了某个类型的常量或变量,就不能再用相同的名称声明它,也不能将其更改为存储不同类型的值。也不能把一个常量变成一个变量或者把一个变量变成一个常量。
注意:如果您需要为常量或变量提供与保留的Swift关键字相同的名称,那么在使用该关键字作为名称时,请在其周围加上反引号(')。但是,除非别无选择,否则不要使用关键字作为名称。
Printing Constants and Variables
你可以使用 print(_:separator:terminator:) 函数 打印出一个常量或变量的值
var friendlyWelcome = "Hello!"
let languageName = "Swift"
print(friendlyWelcome)
print(languageName)
print(:separator:terminator:)函数是一个全局函数,它将一个或多个值打印到适当的输出。例如,在Xcode中,print(:separator:terminator:)函数在Xcode的“console”窗格中打印输出。分隔符和终止符参数有默认值,因此在调用此函数时可以省略它们。默认情况下,函数通过添加换行符来终止它打印的行。要打印一个后面没有换行符的值,传递一个空字符串作为结束符——例如,print(someValue, terminator: "")。有关具有默认值的参数的信息,请阅读 Functions 篇中 Default Parameter Values。
Swift使用字符串插值将常量或变量的名称包含在更长的字符串中作为占位符,并提示Swift将其替换为该常量或变量的当前值。将名称括在圆括号内,并在开括号前加上反斜杠转义:
var friendlyWelcome = "Bonjour!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// Prints "The current value of friendlyWelcome is Bonjour!"
注意:所有可以与字符串插值一起使用的选项都在 Strings and Characters 篇中的 String Interpolation 描述。
Comments 注释
使用注释在代码中包含非可执行文本,作为注释或提醒。代码编译时,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. */
嵌套的多行注释使您能够快速、轻松地注释出大块代码,即使代码已经包含多行注释。
Semicolons 分号 “;”
与许多其他语言不同,Swift不要求在代码中的每个语句后面都要写分号(;),不过如果愿意,也可以这样做。但是,如果你想在一行中写多个独立的语句,分号是必须的:
let cat = "🐱"; print(cat)
Integers 整数
整数是没有分数成分的整数,如42和-23。整数要么有符号(正、零或负),要么无符号(正或零)。
Swift以8、16、32和64位形式提供带符号和无符号整数。这些整数遵循类似于C的命名约定,其中8位无符号整数的类型为UInt8, 32位有符号整数的类型为Int32。与Swift中的所有类型一样,这些整数类型的名称都是大写的。
Integer Bounds 整数边界
您可以访问每个整数类型的最小值和最大值及其最小和最大属性:
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-bit 平台, Int 等于 Int32.
- 在 64-bit 平台, Int 等于 Int64.
除非需要处理特定大小的整数,否则在代码中始终对整数值使用Int。这有助于代码的一致性和互操作性。即使在32位平台上,Int也可以存储-2,147,483,648到2,147,483,647之间的任何值,并且对于许多整数范围来说足够大。
UInt 无符号整型
Swift还提供了一个无符号整数类型UInt,它的大小与当前平台的原生字大小相同:
- 在 32-bit 平台, UInt 等于 UInt32.
- 在 64-bit 平台, UInt 等于 UInt64.
注意:只有当您特别需要与平台的本机字大小相同的无符号整数类型时,才使用UInt。如果不是这种情况,则首选Int,即使已知要存储的值是非负的。整数值使用Int的一致性有助于代码的互操作性,避免了在不同的数字类型之间进行转换的需要,并匹配整数类型推断,如 Type Safety and Type Inference 中所述。
Floating-Point Numbers 浮点数
浮点数是具有分数成分的数,如3.14159、0.1和-273.15。
浮点类型可以表示比整数类型大得多的值范围,并且可以存储比整数类型大得多或小得多的数。Swift提供了两种带符号的浮点数类型:
- Double 代表 64-bit 浮点数
- Float 代表 32-bit 浮点数
注意:Double的精度至少为15位小数,而Float的精度可以低至6位小数。要使用的适当浮点类型取决于代码中需要处理的值的性质和范围。在两种类型都合适的情况下,Double是首选。
Type Safety and Type Inference 类型安全性和类型推断
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。
Numeric Literals 数字字面值
整型文字可以写成:
- 十进制数 没有前缀
- 二级制数 前缀为0b
- 八进制数 前缀为0o
- 十六进制数 前缀为0x
十进制数值17的所有进制表示如下:
let decimalInteger = 17
let binaryInteger = 0b10001 // 17 二进制表示
let octalInteger = 0o21 // 17 八进制表示
let hexadecimalInteger = 0x11 // 17 十六进制表示
浮点文字可以是十进制(没有前缀),也可以是十六进制(带有0x前缀)。
它们必须在小数点的两边都有一个数字(或十六进制数字)。十进制浮点数也可以有一个可选的指数,由大写或小写e表示;
十六进制浮点数必须有一个指数,用大写或小写p表示。
对于exp为指数的小数,结果为底数乘以10的exp次方:
- 1.25e2 表示 1.25 乘以 10的2次方 也就是125.0
- 1.253-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
Numeric Type Conversion 数值类型转换
对代码中的所有通用整数常量和变量使用Int类型,即使已知它们是非负的。在日常情况下使用默认的整数类型意味着整数常量和变量在代码中可以立即互操作,并且将匹配整数文字值的推断类型。
只有当手头的任务特别需要其他整数类型时,才使用它们,因为来自外部源的显式大小的数据,或者为了性能、内存使用或其他必要的优化。在这些情况下使用显式大小的类型有助于捕获任何意外值溢出,并隐式地记录所使用数据的性质。
Integer Conversion 整数转换
可以存储在整数常量或变量中的数字的范围因每种数字类型而异。Int8常量或变量可以存储-128到127之间的数字,而UInt8常量或变量可以存储0到255之间的数字。当你编译代码时,一个不适合常量或整数大小的变量的数字被报告为错误:
let cannotBeNegative: UInt8 = -1
// UInt8 不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1
// Int8 不能存储超过其最大值的数,所以会报错
由于每种数值类型都可以存储不同范围的值,因此必须根据具体情况选择数值类型转换。这种选择进入方法可以防止隐藏的转换错误,并有助于在代码中显式地显示类型转换意图。
要将一种特定的数字类型转换为另一种类型,需要使用现有值初始化所需类型的新数字。在下面的示例中,常量2000的类型是UInt16,而常量1的类型是UInt8。它们不能直接相加,因为它们不是同一类型的。相反,这个例子调用UInt16(one)来创建一个新的初始值为1的UInt16,并使用这个值代替原来的值:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因为加法的两边现在都是UInt16类型,所以加法是允许的。输出常数(2000和1)被推断为UInt16类型,因为它是两个UInt16值的和。
SomeType(ofInitialValue)是调用Swift类型初始化器并传入初始值的默认方法。在幕后,UInt16有一个接受UInt8值的初始化器,因此这个初始化器用于从现有的UInt8生成一个新的UInt16。但是,这里不能传递任何类型——它必须是UInt16提供初始化器的类型。扩展中包括扩展现有类型以提供接受新类型(包括您自己的类型定义)的初始化器。
Integer and Floating-Point Conversion 整数和浮点数类型转换
整数和浮点数类型之间的转换必须显式:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double
使用Double给three进行了类型转换,使用的 + 运算符两边类型相同,否则会报错。
浮点数转为整数,也必须使用显示转换。
let integerPi = Int(pi)
当用这种方法初始化一个新的整数值时,浮点值总是被截断。这意味着4.75变成4 , -3.9变成-3。
组合数值常量和变量的规则与数值文字的规则不同。文字值3可以直接添加到文字值0.14159,因为数字文字本身没有显式的类型。它们的类型仅在编译器计算它们时才推断出来。
Type Aliases 类型别名
使用 typealias 关键字可以给现有类名定义别名
当您希望使用上下文更合适的名称引用现有类型时,例如处理来自外部源的特定大小的数据时,类型别名非常有用:
typealias AudioSample = UInt16
一旦定义了类型别名,就可以在任何可能使用原始名称的地方使用别名:
var maxAmplitudeFound = AudioSample.min
这里,AudioSample被定义为UInt16的别名。因为它是别名,调用AudioSample.min实际上调用UInt16.min,它为maxAmplitudeFound变量提供了一个初始值0。
Booleans 布尔值
Swift有一个基本的布尔类型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.")
}
条件语句(如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中的其他类型安全示例一样,这种方法避免了意外错误,并确保特定代码段的意图始终清晰。
Tuples 元组 (Swift中新概念)
元组将多个值分组为一个复合值。元组中的值可以是任何类型的,而且不必是彼此相同的类型。
在本例中,(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")
(404,“Not Found”)元组将一个Int和一个字符串组合在一起,为HTTP状态代码提供两个单独的值:一个数字和一个人类可读的描述。它可以被描述为“类型(Int, String)的元组”。
您可以从任何类型的排列中创建元组,它们可以包含任意数量的不同类型。没有什么可以阻止您拥有类型(Int、Int、Int)或(String、Bool)的元组,或者任何您需要的其他排列。
您可以将元组的内容分解为单独的常量或变量,然后像往常一样访问这些常量或变量:
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
print("The status message is \(statusMessage)")
如果你只需要元组的部分值,可以使用下划线替代那些不需要的值:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
或者,使用索引号从零开始访问元组中的单个元素值:
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)")
print("The status message is \(http200Status.description)")
元组作为函数的返回值特别有用。试图检索web页面的函数可能返回(Int, String)元组类型来描述检索页面的成功或失败。通过返回具有两个不同值的元组(每个值都属于不同的类型),函数提供了关于其结果的更有用的信息,而不是只返回单一类型的单个值。有关更多信息,请阅读 Functions with Multiple Return Values。
注意:元组对于相关值的临时组非常有用。它们不适合创建复杂的数据结构。如果您的数据结构可能会持续存在于临时范围之外,请将其建模为类或结构,而不是元组。有关更多信息,请阅读 Structures and Classes 。
Optionals 可选类型
在可能没有值的情况下使用 optional 。
表示两种可能性:
- 要么有一个值,您可以打开可选来访问该值,
- 要么根本没有值。
optional 的概念在C或Objective-C中并不存在。Objective-C中最接近的东西是能够从一个方法返回nil否则这个方法会返回一个对象,nil的意思是"缺少一个有效的对象"然而,这只适用于对象——它不适用于结构、基本C类型或枚举值。对于这些类型,Objective-C方法通常返回一个特殊的值(如NSNotFound)来表示没有值。这种方法假设方法的调用者知道要测试一个特殊的值,并记得检查它。Swift的optional 允许您指出任何类型都没有值,而不需要特殊的常量。
下面是一个示例,说明如何使用 optional 来处理缺少值的情况。Swift的Int类型有一个初始化器,它尝试将字符串值转换成Int值。然而,并不是每个字符串都可以转换成整数。字符串“123”可以转换为数值123,但是字符串“hello, world”没有明显的数值可以转换。
下面的示例使用初始化器尝试将字符串转换为Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
convertedNumber被推断为类型为“Int?”,或“optional Int”
因为初始化器可能会失败,所以它返回一个 optional Int,而不是Int。问号表示它包含的值是可选的,这意味着它可能包含一些Int值,也可能根本不包含任何值。(它不能包含任何其他东西,比如Bool值或字符串值。它要么是一个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?
Swift的nil不等于Objective-C中的nil。在Objective-C中,nil是指向不存在对象的指针。在Swift中,nil不是指针——它是缺少某种类型的值。任何类型的选项都可以设置为nil,而不仅仅是对象类型。
If Statements and Forced Unwrapping if语句和强制展开
可以使用if语句通过将可选值与nil进行比较来确定可选值是否包含值。您可以使用“等于”操作符(==)或“不等于”操作符(!=)执行此比较。
果一个可选的有一个值,它被认为是“不等于”nil:
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
一旦确定 optional 确实包含值,就可以通过在 optional 名称的末尾添加感叹号(!)来访问其基础值。感叹号有效地表示,“我知道这个可选项肯定有一个值;请使用它。这被称为强制展开可选类型的值:
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."
关于 if 语句更多信息,请阅读 Control Flow。
注意:尝试使用 ! 若要访问不存在值的optional,将触发运行时错误。在使用 ! 强制展开其值之前,请始终确保optional含非nil值。
Optional Binding 可选绑定
您可以使用可选绑定来确定可选项是否包含值,如果包含,则使该值作为临时常量或变量可用。可选绑定可以与if和while语句一起使用,以检查可选语句中的值,并将该值提取为常量或变量,作为单个操作的一部分。if和while语句在 Control Flow 中描述得更详细。
为if语句编写一个可选绑定,如下所示:
if let constantName = someOptional {
statements
}
您可以使用可选绑定来重写上面的 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")
}
输出 "The string "123" has an integer value of 123"
此代码可读作:
如果 Int(possibleNumber) 返回的可选Int包含一个值,则将一个名为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中所述。
Implicitly Unwrapped Optionals 隐式展开可选
如上所述,optional表示允许常量或变量具有“无值”。可以使用if语句检查optional是否存在值,也可以使用可选绑定有条件地解除包装,以便在可选值存在时访问该可选值。
有时候从程序的结构来看,一个可选类型从创建之后总有一个值。
在这种情况下:每次访问可选类型是可以不需要检查和展开可选类型,因为安全地认为optional一直有个值。
这些类型的选项被定义为隐式展开的选项。通过在要使其成为可选的类型后面放置感叹号(String!)而不是问号(String?),可以编写隐式展开的可选类型。
当一个可选项的值在第一次定义可选项之后立即被确认为存在时,隐式展开的选项是有用的,并且可以肯定地假设在此后的每一点都存在。Swift中隐式展开选项的主要用途是在类初始化期间,如 Unowned References and Implicitly Unwrapped Optional Properties 中所述那样。
隐式展开的可选值在幕后是一个普通的可选值,但也可以像非可选值一样使用,而不需要在每次访问时展开可选值。下面的例子显示了一个可选字符串和一个隐式未包装的可选字符串之间的行为差异,当访问它们的包装值作为显式字符串时:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! 必须用!展开
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString 不用!展开
您可以将隐式展开optional 视为在使用optional 时自动展开。每次使用optional 时,不要在其名称后面加上感叹号,而是在声明时在其类型后面加上感叹号。
注意:如果隐式展开的optional 为nil ,并且您试图访问它的包装值,您将触发一个运行时错误。结果与在不包含值的常规可选项后面放置感叹号完全相同。
您仍然可以像对待普通的optional一样对待隐式展开的optional,以检查它是否包含一个值:
if assumedString != nil {
print(assumedString!)
}
还可以使用带可选绑定的隐式展开可选项,在一条语句中检查并展开其值:
if let definiteString = assumedString {
print(definiteString)
}
注意:当变量可能在稍后变为nil时,不要使用隐式展开的可选变量。如果需要在变量的生命周期内检查nil值,请始终使用普通的可选类型。
Error Handling 错误处理
您使用错误处理来响应程序在执行过程中可能遇到的错误条件。
与optionals不同,optionals可以使用值的存在或不存在来传递函数的成功或失败,错误处理允许您确定失败的潜在原因,并在必要时将错误传播到程序的另一部分。
当函数遇到错误条件时,它会抛出一个错误。然后,该函数的调用者可以捕捉错误并做出适当的响应。
func canThrowAnError() throws {
// this function may or may not throw an error
}
函数表示它可以通过在声明中包含throw关键字来抛出错误。当调用可能引发错误的函数时,请在表达式前加上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 匹配。然后使用catch模式捕获的关联[String]值调用 buyGroceries(_:) 函数。
在 Error Handling 篇中更详细地讨论了抛出、捕获和传播错误。
Assertions and Preconditions 断言和先决条件
断言和先决条件是运行时发生的检查。
在执行任何其他代码之前,使用它们来确保满足基本条件。
如果断言或先决条件中的布尔条件计算为true,则代码执行照常进行。如果条件计算为false,则程序的当前状态无效;代码执行结束,应用程序终止。
您可以使用断言和先决条件来表达您在编码时所做的假设和期望,因此您可以将它们包含在代码中。断言可以帮助您在开发过程中发现错误和不正确的假设,而先决条件可以帮助您检测生产中的问题。
除了在运行时验证您的期望之外,断言和先决条件也成为代码中有用的文档形式。与上面错误处理中讨论的错误条件不同,断言和前置条件不用于可恢复的或预期的错误。因为失败的断言或先决条件指示无效的程序状态,所以无法捕获失败的断言。
使用断言和先决条件并不能替代以不太可能出现无效条件的方式设计代码。然而,使用它们来强制执行有效的数据和状态会使您的应用程序在出现无效状态时更容易终止,这有助于使问题更容易调试。一旦检测到无效状态,立即停止执行也有助于限制该无效状态造成的损害。
断言和先决条件之间的区别在于检查它们的时候:
断言只在调试构建中检查,而先决条件在调试和生产构建中都检查。在生产构建中,不计算断言中的条件。这意味着您可以在开发过程中使用任意多的断言,而不会影响生产中的性能。
Debugging with Assertions 调试断言
通过调用Swift标准库中的assert(::file:line:)函数来编写断言。将计算结果为true或false的表达式传递给这个函数,如果条件的结果为false,将显示一条消息。例如:
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
该断言失败,因为 -3>=0 是错误的。
在本例中,如果age >=0 是正确的,那么程序继续执行,否则程序终止。
您可以省略断言消息—例如,当它只是以散文形式重复条件时。
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.")
}
Enforcing Preconditions 执行先决条件
如果条件可能为false,但是必须为true时代码才能继续执行,那么就使用先决条件。例如,使用先决条件检查下标是否超出界限,或者检查函数是否传递了有效值。
您可以通过调用 precondition(::file:line:)函数来编写先决条件。将计算结果为true或false的表达式传递给这个函数,如果条件的结果为false,将显示一条消息。例如:
precondition(index > 0, "Index must be greater than zero.")
您还可以调用preconditionFailure(_:file:line:)函数来指示发生了故障——例如,如果采用了switch的默认情况,但是所有有效的输入数据都应该由switch的其他情况之一处理。
注意:
- 如果在未选中模式(-Ounchecked)下编译,则不检查先决条件。编译器假设先决条件始终为真,并相应地优化代码。但是,无论优化设置如何,fatalError(_:file:line:)函数总是会停止执行。
- 您可以在原型化和早期开发期间使用fatalError(_:file:line:)函数为尚未实现的功能创建存根,方法是将fatalError(“unimplementation”)编写为存根实现。因为与断言或先决条件不同,致命错误永远不会被优化掉,所以可以确保在遇到存根实现时执行总是会停止。
网友评论