Swift 4官方文档中文版: The Basic(下)

Swift学习有问必答群 : 313838956 ( mac版QQ有权限要求, 入群只能通过手机版 QQ申请). 本群由Guards翻译组创建并维护
入群须知:

0.0 重申: mac版QQ有权限要求, 入群只能通过手机版 QQ申请.
0.1 群主晚上每天20点--21点定时回答Swift相关的问题.
0.2 群主会在看到问题后, 第一时间回复
0.3 拒绝长时间潜水, 拒绝讨论和Swift , iOS 无关的问题



该文章翻译自Apple官方文档: The Swift 4 Programming Language

Guards翻译组 正在翻译Swift 4的全套文档, 这是该文档第一章节《The Basics》的下半部分, 上半部分的译文 请点这里, 原文链接: The Basics

翻译 : Nc / Aaron
校对: August / Jonhory

译者心声

我们会不定期的更新翻译文章, Guards翻译组下周会发布 Basic Operators 章节中文版. 如感兴趣,可以关注我们的简书

我们是一群热爱翻译并且热爱 Swift 的人, 希望通过自己的努力让不熟悉英语的程序员也能学习到国外的高质量的文章. 如发现文章中翻译错误之处, 烦请跟我们联系, 我们第一时间更正.

本篇包含内容:

  • 可选类型
  • Nil 类型
  • If语句和强制解包
  • 可选绑定
  • 隐式解包可选类型
  • 错误处理
  • 断言和先决条件
  • 用断言进行调试
  • 执行先决条件

可选类型

值可能不存在的情况下,你可以使用可选类型。 一个可选类型代表两种可能性:要么它有值(你可以通过解包来访问该值), 或者没有值。

Note: 在C或Objective-C中不存在可选类型的概念。 在OC中和可选类型这个概念最类似的是: 一个对象要么返回确定的值要么返回nil,其中nil表示“对象不存在”。然而这种方法仅适用于对象,并不适用于结构体、C的基本类型以及枚举。 对于这些类型,OC方法通常返回一个特殊值(例如NSNotFound)来表示没有值。 这种机制假设了方法调用者知道存在一个特殊的值,而且会通过这个值来检测方法的结果。Swift 的可选类型可以让你表示任意类型的值缺失,并不需要特殊值。

这有一个例子说明了如何使用可选类型来处理不存在的值。 Swift的Int类型有一个String类型的值转换成Int类型的初始化方法。 但是,并不是每个字符串都可以转换为Int类型。 字符串"123"可以转换为数值123,但字符串hello,world明显没有数字值可转换为Int

以下的示例使用这个初始化方法尝试将一个String转换为Int

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber被转换为类型"Int?",即"可选类型Int"

因为初始化方法可能会失败,所以它返回的是一个可选类型Int,而不是Int。 一个可选类型Int我们用Int?表示,而不是Int。 问号表示这个Int它包含的值是可选的,这意味着它可能包含一些具体的Int值,或者它可能不包含任何值。 (它不能包含任何其他值,例如Bool值或String值,它要么就是一个Int,要么它根本就没有值)

Nil

通过赋值nil,你可以将一个可选类型设置为无值状态:

var serverResponseCode:Int? = 404
// serverResponseCode包含一个具体的Int值,即404
serverResponseCode = nil
// serverResponseCode现在不包含任何值

NOTE: nil不能和非可选类型的常量和变量一起使用。 如果你的代码中的常量或变量在某些情况下需要以没有值的状态运行,那你应该始终将其声明为可选类型。

如果你定义了一个可选类型的变量, 但是不设置默认值,那么该变量的值将自动设置为nil

var surveyAnswer:String?
// surveyAnswer自动设置为nil

NOTE: Swift中的nil不同于OC中的nil。 在OC中,nil 是一个指向不存在的对象的指针。 在Swift中,nil不是指针, 它是一个不存在值的特定类型的。 任何数据类型的可选类型都可以设置为 nil,而不仅仅是对象类型。

If语句和强制解包

你可以通过使用if语句比较可选类型和nil,进而确定这个可选类型是否包含具体的值。你可以使用等于 运算符==不等于 运算符!=执行比较。

如果一个可选类型有一个值,即它被认为是“不等于”nil

if convertedNumber != nil {
    print(“convertedNumber包含一些整数值”)
}
//打印结果:“convertedNumber包含一些整数值”。

一旦你确定可选类型确实有值,你可以通过在可选类型名称的末尾添加感叹号(!)来访问其包含的值。这个感叹号如同在宣称:“ 我知道这个可选类型肯定有值,请使用它。” 这被称为可选类型的强制解包:

if convertedNumber != nil {
    print(“convertedNumber的值为\(convertedNumber!)”)
}
// 打印结果: “convertedNumber的值为123”

关于if语句的更多信息,请参阅控制流(Control Flow)

NOTE:
使用 (!) 时,访问不存在值的可选类型会触发运行错误, 所以在使用(!) 强制解包前, 要始终确保可选类型包含了非空的值。

可选绑定

你可以使用可选绑定来确定可选类型是否包含值,如果这样做,你必须把这个绑定的值用作临时常量或变量。可选绑定可以与if-while 语句一起使用,以检查可选类型中的值,并作为这个操作的一部分将该值提取为常量或变量。if-while语句在控制流(Control Flow)中有更详细的描述。

if语句编写可选绑定如下所示:

  if let constantName = someOptional {
    //statements
}

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

if let actualNumber = Int(possibleNumber){
     print("\"\(possibleNumber)\"的整数值为\(actualNumber)")
} else {
     print("\"\(possibleNumber)\"无法转换为Int类型")
}
// 打印结果: ”123“的整数值为123“

这段代码可以这样理解:
“如果Int(possibleNumber)方法返回的可选Int包含一个值,创建一个名为actualNumber的新常量并设置其值为可选类型包含的值。”

如果转换成功,则常量actualNumber可用于if语句的第一个分支中。它已经通过可选类型中的值初始化了,所以没有必要使用!后缀访问其值。在这个例子中,actualNumber只简单地用于打印转换后的结果。

你可以同时将常量和变量与可选绑定一起使用。如果要在if语句的第一个分支中操作actualNumber的值,你可以编写if var actualNumber来替代原来的写法,并且可选类型中包含的值将作为变量而不是常量使用。

你可以在单个if语句中按你的需求包含多个可选绑定和Boolean条件,并以逗号分隔。如果可选绑定中的任何值为nil或任何Boolean条件为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”

NOTE:
if语句中使用可选绑定创建的常量和变量仅在if语句的主体内可用。
相比之下,使用guard语句创建的常量和变量可以在guard语句后面
的代码中使用,如(Early Exit)所述。

隐式解包可选类型

如上所述,使用可选类型意味着允许常量或变量可以“无值”。使用if语句可以查看可选类型的值是否存在,并且可以使用可选绑定解包以访问存在的可选类型值。

在设置了初始值后,有时从程序的结构中可以清楚地看到,可选类型将始终有值。在这种情况下,每次访问可选类型的值时没有必要进行检查和解包,因为我们可以安全地假定任何情况下它都是有值的。

这种可选类型被定义为隐式解包可选类型。通过在想要设置为可选类型的参数后添加一个感叹号(String!),而不是一个问号(String?),你就可以得到一个隐式解包的可选类型。(译者注: 即隐式解包即常说的 强制解包)

当可选类型在初始化后确认其值肯定存在,并且可以肯定地假设存在于之后的任何时刻,那么隐式解包可选类型将提供很大的帮助。在Swift中使用隐式解包可选类型主要是在初始化类的时候,如Unowned References and Implicitly Unwrapped Optional Properties 所述。

隐式解包可选类型是一个常规选择,但也可以像非可选类型一样使用,无需在每次访问时进行解包。 以下示例展示了当访问可选String类型与隐式解包可选String类型的一个具体String值时, 两者的差异:

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 要求加上!来解包
 
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要加上!也可以解包

你可以将 隐式解包可选类型 看作是给予一个可选类型的权利,在任何它被使用的时候它都能自动解包。 比起在每次使用可选类型时加上一个感叹号时,你只需要在声明它时在其后添加一个感叹号。

NOTE:
如果一个隐式解包可选类型的值为nil,当你尝试访问其值时则会触发
运行时错误。 其原因与给一个值为空的常规可选类型添加感叹号的情形完全相同。

你仍然可以将隐式解包可选类型视为常规的可选类型,以检查它是否包含一个值:

if assumedString != nil {
    print(assumedString)
}
// 打印结果: "An implicitly unwrapped optional string.”

你还可以使用隐式解包可选类型与可选绑定,在单个语句中检查和解包其值:

if let definiteString = assumedString {
    print(definiteString)
}
// 打印结果: "An implicitly unwrapped optional string.”

NOTE:
当一个变量在以后有可能为nil时,不要使用隐式解包可选类型。在变
量的生命周期内如果需要检查它是否为nil,请始终使用常规的可选类型。

错误处理

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

与可选类型相反,错误处理可以通过值的存在或不存在来表示一个function的成功或失败,允许你确定error的根本原因,如有必要,它还能将error传送到程序的另一部分。

当方法遇到错误条件时,会抛出一个error。 该方法的调用者可以捕获错误并作出适当的响应。

func canThrowAnError() throws {
     //这个函数可能会抛出一个错误
}

一个函数通过在其声明中包含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()方法就会抛出error。我们将该函数的调用包装在try表达式中。 通过do语句中包装并调用函数,抛出的所有错误将被传送到catch语句进行处理。

如果没有错误发生,那么eatASandwich()方法就会被调用。 如果抛出一个错误并且匹配到SandwichError.outOfCleanDishes,那么washDishes()函数将会被调用。 如果抛出的错误匹配到SandwichError.missingIngredients,则会调用buyGroceries(_ :)方法,并把catch捕获的关联值(String类型)传给它。

错误处理的章节对抛出,捕获和传送错误进行了更详细的描述。

断言Assertions和先决条件Preconditions

断言和先决条件是会在运行时进行的检查手段。在执行任何其他代码之前,你可以使用它们来确保基本条件已经满足。如果断言或前提条件中的布尔条件为true,则代码照常执行。如果条件为false,那么程序当前处于无效状态;代码会结束执行,你的应用程序也会被终止。

写代码时,使用断言和先决条件你可以表达出你需要的期望条件,因此你可以将它们作为代码的一部分。在开发过程中,断言可帮助你发现错误和不正确的假设条件,先决条件可帮助你检测发布后的问题。除了在运行时验证你的期望条件外,断言和先决条件也是代码中一种有效的文档形式。与先前在错误处理中讨论的错误条件不同,断言和先决条件不用于可恢复的或期望的错误,因为失败的断言或先决条件表示无效的程序状态,所以无法捕获失败的断言。

使用断言和先决条件并不意味着在你的代码中无效条件就一定不会出现。 然而,使用它们来强制执行有效的数据,会使你的应用程序在错误发生并被强制终止时,更具备可监测性,而且有助于调试。 一旦检测到无效状态,停止执行也有助于将其引起的损坏最小化。

断言和先决条件之间的区别在于被执行的时间点有所不同:断言仅在调试版本中执行,但在调试和发布版本中先决条件都会被执行。 在发布版本中,断言内的条件不再被执行。 这意味着你可以在开发过程中使用尽可能多地使用断言,因为这并不会影响发布版本的性能。

用断言进行调试

通过从Swift标准库调用assert(_:_:file:line:)方法,你写下一个断言。 你将一段文字和一个结果为truefalse的表达式赋值给这个方法,如果表达式的结果为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的值为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.")
}
执行先决条件

当条件有可能为false的时候使用先决条件,但是必须确保你的代码会继续执行。 例如,你可以使用先决条件来判断下标是否越界,或者检查方法是否接收到一个有效的值。

通过调用precondition(_:_:file:line:)方法你可以编写先决条件。 你需要给此方法赋值一个表达式,其计算结果为truefalse,如果条件的结果为false,则显示一段文字(也是由你赋值)。 例如:

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

你还可以调用preconditionFailure(_:file:line:)方法来标明错误的出现。例如,如果所有输入的有效数据都应该被switchcase语句执行, 但程序执行了switch结构中的default语句。

NOTE: 如果在-Ounchecked模式下进行编译,则不执行先决条件。 编译器假设先决条件始终为真,并优化你的相应的代码。 但是无论如何设置,fatalError(_:file:line:)方法总是不执行。

在早期开发中你可以使用fatalError(_:file:line:)方法给你还未实现的功能创建存根,例如,使用fatalError("Unimplemented")方法作为方法的实现。 与断言或前提条件不同,因为致命错误从未被优化,你可以确保程序在执行如果遇到存根实现一定会终止。

推荐阅读更多精彩内容