iOS 学习 Swift 教程- 2.1 Swift 基础部分(The Basics)

前言:
本篇文章的目的,在于记录学习过程,敦促自己,方便查看。

练习工具:Playground
学习网站: swift51

本页内容包括:

常量和变量

常量的值一旦设定就不能改变,而变量的值可以随意更改。

声明常量和变量

常量和变量必须在使用前声明,用 let 来声明常量,用 var 来声明变量。

下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:

 let maximumNumberOfLoginAttemps = 10
 var currentLoginAttempt = 0

这两行代码可以被理解为:

 1.0 声明一个名字是 maximumNumberOfLoginAttempts 的新常量,并给它一个值 10 。
 2.0 声明一个名字是  currentLoginAttempt 的变量并将它的值初始化为 0 。

在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。

你可以在一行中声明多个常量或者多个变量,用逗号隔开:

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

类型标注

当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号空格,然后加上类型名称

这个例子给 welcomeMessage 变量添加了类型标注,表示这个变量可以存储 String 类型的值:

var welcomeMessage: String

声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:

 1.0 声明一个类型为 String ,名字为 welcomeMessage 的变量。
 2.0 类型为 String ”的意思是“可以存储`任意` String 类型的值

welcomeMessage 变量现在可以被设置成任意字符串

 welcomeMessage = "Hello"

你可以在一行中定义多个同样类型变量,用逗号分割,并在最后一个变量名后添加类型标注

var red, green, blue: Double

注意:

一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值Swift可以推断出这个常量或者变量类型,请参考类型安全和类型推断。在上面的例子中,没有给 welcomeMessage 赋初始值,所以变量 welcomeMessage 的类型通过一个类型标注指定的,而不是通过初始值推断的

常量和变量的命名

你可以用任何你喜欢的字符作为常量和变量名,包括 Unicode 字符

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

常量与变量名不能包含数学符号箭头保留的(或者非法的)Unicode 码位连线与制表符也不能数字开头,但是可以常量与变量名的其他地方包含数字

一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。

注意:

如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号 (`) 将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。

你可以更改现有的变量值为其他同类型的值,在下面的例子中,friendlyWelcome的值从"Hello!"改为了"Bonjour!":

 var friendlyWelcome = "Hello!"
 friendlyWelcome = "Bonjour!"
 // friendlyWelcome 现在是 "Bonjour!"

与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:

 let languageName = "Swift"
 languageName = "Swift++"
 // 这会报编译时错误 - languageName 不可改变

输出常量和变量
你可以用print(_:separator:terminator:)函数来输出当前常量或变量的值:

 print(friendlyWelcome)
 // 输出 "Bonjour!"

print(_:separator:terminator:)是一个用来输出一个或多个值到适当输出区的全局函数。如果你用Xcode,print(_:separator:terminator:) 将会输出内容到“console”面板上。separatorterminator参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符结束当前行。如果不想换行,可以传递一个空字符串terminator参数--例 如,print(someValue, terminator:"") 。关于参数默认值的更多信息,请参考默认参数值

Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量变量名放入圆括号中,并在开括号前使用反斜杠将其转义

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

注意
字符串插值所有可用的选项,请参考字符串插值

注释
请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。

Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠(//)作为起始标记:

 // 这是一个注释

你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(/*),终止标记为一个星号后跟随单个正斜杠(*/):

 /* 这是一个, 多行注释 */

与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:

 /* 这是第一个多行注释的开头 /* 这是第二个被嵌套的多行注释 */ 这是第一个多行注释的结尾 */

通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。

分号
与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号(;),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句

 let cat = "🐱";print(cat)
 // 输出 "🐱"

整数
整数就是没有小数部分的数字,比如 42 和 -23 。整数可以是 有符号(正、负、零)或者 无符号(正、零)。

Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是UInt8,32位有符号整数类型是 Int32 。就像 Swift 的其他类型一样,整数类型采用大写命名法

整数范围
你可以访问不同整数类型的 min 和 max 属性来获取对应类型的最小值和最大值:

 let minValue = UInt8.min   // minValue 为 0,是 UInt8 类型
 let maxValue = UInt8.max   // maxValue 为 255,是 UInt8 类型

minmax所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。

Int
一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型Int,长度与当前平台的原生字长相同:

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

除非你需要特定长度的整数,一般来说使用 Int 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,Int 可以存储的整数范围也可以达到-2,147,483,648 ~ 2,147,483,647,大多数时候这已经足够大了。

UInt
Swift 也提供了一个特殊的无符号类型UInt,长度与当前平台的原生字长相同:

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

注意:

尽量不要使用UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用Int,即使你要存储的值已知是非负的。统一使用Int可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考类型安全和类型推断

浮点数

浮点数是有小数部分的数字,比如 3.14159 ,0.1 和 -273.15。

浮点类型比整数类型表示的范围更大,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:

  • Double表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
  • Float表示32位浮点数精度要求不高的话可以使用此类型。

注意:
Double精确度很高至少15位数字,而Float只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 Double

类型安全和类型推断
Swift 是一个类型安全(type safe)的语言。类型安全的语言可以让你清楚地知道代码处理的值的类型。如果你的代码需要一个String,你绝对不可能不小心传进去一个Int

由于 Swift类型安全的,所以它会在编译你的代码时进行类型检查``(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。

当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型Swift 会使用类型推断(type inference)选择合适的类型。有了类型推断编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。

因为有类型推断,和 C 或者 Objective-C比起来 Swift很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成

当你声明常量或者变量赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的,比如 423.14159 。)

例如,如果你给一个新常量赋值 42 并且没有标明类型Swift 可以推断常量类型Int ,因为你给它赋的初始值看起来像一个整数

 let meaningOfLife = 42
  // meaningOfLife 会被推测为 Int 类型

同理,如果你没有给浮点字面量标明类型Swift 会推断你想要的是 Double

 let pi = 3.14159
 // pi 会被推测为 Double 类型

当推断浮点数的类型时,Swift 总是会选择Double 而不是Float

如果表达式中同时出现整数浮点数,会被推断Double 类型:

 let anotherPi = 3 + 0.14159
 // anotherPi 会被推测为 Double 类型

原始值3 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断Double 类型。

数值型字面量

整数字面量可以被写作:

1. 一个十进制数,`没有前缀`
2. 一个二进制数,前缀是`0b`
3. 一个八进制数,前缀是`0o`
4. 一个十六进制数,前缀是`0x`

下面的所有整数字面量的十进制值都是17:

 let decimalInteger = 17
 let binaryInteger = 0b10001       // 二进制的17
 let octalInteger = 0o21           // 八进制的17
 let hexadecimalInteger = 0x11     // 十六进制的17
  • 浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 0x)。小数点两边必须至少一个十进制数字(或者是十六进制的数字)。
  • 十进制浮点数也可以有一个可选的指数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。

下面的这些浮点字面量都等于十进制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

数值型类型转换
通常来讲,即使代码中的整数常量和变量已知非负,也请使用Int类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量类型推断

只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据

整数转换

不同整数类型的变量和常量可以存储不同范围的数字。Int8类型的常量或者变量可以存储的数字范围是-128~127,而UInt8类型的常量或者变量能存储的数字范围是0~255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错

 let cannotBeNegative: UInt8 = -1
 // UInt8 类型不能存储负数,所以会报错
 let tooBig: Int8 = Int8.max + 1
 // Int8 类型不能存储超过最大值的数,所以会报错

由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。

要将一种数字类型转换另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量twoThousandUInt16类型,然而常量oneUInt8类型。它们不能直接相加,因为它们类型不同。所以要调用UInt16(one)来创建一个新的UInt16数字并用one的值来初始化,然后使用这个新数字来计算
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 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考扩展

整数和浮点数转换
整数浮点数转换必须显式指定类型

 let three = 3
 let pointOneFourOneFiveNine = 0.14159
 let pi = Double(three) + pointOneFourOneFiveNine
 // pi 等于 3.14159,所以被推测为 Double 类型

这个例子中,常量three的值被用来创建一个Double 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加

浮点数整数反向转换同样行,整数类型可以用Double或者Float类型来初始化

 let integerPi = Int(pi)
  // integerPi 等于 3,所以被推测为 Int 类型

当用这种方式初始化一个新的整数值时浮点值会被截断。也就是说4.75会变成 4-3.9 会变成-3

注意:
结合 数字类常量变量不同于 结合 数字类字面量。字面量 3可以直接和字面量 0.14159相加,因为数字字面量 本身没有明确的类型。它们的类型只在编译器需要 求值的时候 被推测

类型别名
类型别名 (type aliases)就是给现有类型 定义另一个名字。你可以使用 typealias关键字来 定义类型别名

当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:

 typealias AudioSample = UInt16

定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:

 var maxAmplitudeFound = AudioSample.min
 // maxAmplitudeFound 现在是 0

本例中,AudioSample被定义为UInt16的一个别名。因为它是别名,AudioSample.min实际上是UInt16.min,所以会给maxAmplitudeFound赋一个初值0。

布尔值
Swift有一个基本的布尔(Boolean)类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是或者Swift有两个布尔常量,truefalse

 let orangesAreOrange = true
 let turnipsAreDelicious = false

orangesAreOrangeturnipsAreDelicious 的类型会被推断Bool,因为它们的初值是布尔字面量。就像之前提到的IntDouble一样,如果你创建变量的时候给它们赋值true 或者false,那你不需要将常量或者变量声明为 Bool 类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。

当你编写条件语句比如if语句的时候,布尔值非常有用

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

条件语句,例如if,请参考控制流

如果你在需要使用 Bool 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:

 let i = 1
 if i {
    // 这个例子不会通过编译,会报错
 }

然而,下面的例子是合法的:

 let i = 1
 if i == 1 {
     // 这个例子会编译成功
 }

i == 1 的比较结果是 Bool 类型,所以第二个例子可以通过类型检查。类似 i == 1 这样的比较,请参考基本操作符

和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。

元组
元组(tuples)多个值组合成一个复合值。元组的值可以是任意类型,并不要求是相同类型

下面这个例子中,(404, "Not Found")是一个描述 HTTP 状态码(HTTP status code)元组HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个404 Not Found状态码。

 let http404Error = (404, "Not Found")
 // http404Error 的类型是 (Int, String),值是 (404, "Not Found")

(404, "Not Found")元组把一个Int值和一个 String值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为(Int, String)元组”。

你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 (Int, Int, Int) 或者 (String, Bool)或者其他任何你想要的组合的元组

你可以将一个元组的内容分解(decompose)成单独的常量变量,然后你就可以正常使用它们了:

 let (statusCode, statusMessage) = http404Error
 print("The status code is \(statusCode).")
 // 输出 "The status code is 404"
 print("The status code 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"

你可以在定义元组的时候给单个元素命名:

 let http200Status = (statusCode: 200, description: "OK")

给元组中的元素命名后,你可以通过名字来获取这些元素的值:

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

作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考函数参数与返回值

注意:
元组在临时组织值的时候很有用,但是并不适合创建复杂数据结构。如果你的数据结构并不是临时使用,请使用或者结构体不是元组。请参考类和结构体

可选类型
使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示:

 - 有值,等于 x
 - 没有值

注意:
CObjective-C并没有可选类型这个概念。最接近的是Objective-C中的一个特性,一个方法要不返回一个对象要不返回nilnil表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的C 类型或者枚举类型不起作用对于这些类型,Objective-C 方法一般会返回一个特殊值(比如NSNotFound)暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值

来看一个例子SwiftInt 类型有一种构造器,作用是一个 String转换成一个 Int值。然而,并不是所有的字符串都可以转换成一个整数。字符串 "123"可以被转换数字 123 ,但是字符串 "hello, world" 不行

下面的例子使用这种构造器来尝试将一个String 转换成Int

 let possibleNumber = "123"
 let convertedNumber = Int(possibleNumber)
 // convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"

因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个Int。一个可选的 Int 被写作 Int? 而不是Int问号暗示包含的值可选类型,也就是说可能包含 Int也可能不包含值。(不能包含其他任何值比如 Bool值或者 String值。只能是Int或者什么都没有。)

nil
你可以给可选变量赋值为nil来表示它没有值

 var serverResponseCode: Int? = 404
 // serverResponseCode 包含一个可选的 Int 值 404
 serverResponseCode = nil
 // serverResponseCode 现在不包含值

注意:
nil不能用于非可选常量变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型

如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置nil

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

注意:

SwiftnilObjective-C中的nil不一样。在Objective-C中,nil是一个指向不存在对象的指针。在Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失任何类型的可选状态可以被设置为 nil,不只是对象类型。

if 语句以及强制解析

你可以使用 if语句和 nil比较来判断一个可选值是否包含值。你可以使用“相等”(==)或“不等”(!=)执行比较

如果可选类型有值,它将不等于 nil

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

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping)

 if convertedNumber != nil {
     print("convertedNumber has an integer value of \(convertedNumber!).")
 }
 // 输出 "convertedNumber has an integer value of 123."

更多关于 if 语句的内容,请参考控制流
注意:
使用 !获取一个 不存在可选值会导致运行时 错误。使用 !强制解析值之前,一定要 确定可选包含一个非 nil 的值。

可选绑定
使用*可选绑定(optional binding)*判断可选类型是否包含值,如果包含把值赋给一个临时常量或者变量可选绑定可以用在 ifwhile 语句中,这条语句不仅可以用来判断可选类型中是否有值同时可以可选类型中的值赋给一个常量或者变量ifwhile 语句,请参考控制流
像下面这样在 if语句中写一个可选绑定:

 if let constantName = someOptional {
     statements
 }

你可以像上面这样使用可选绑定来重写 possibleNumber 这个例子

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

这段代码可以被理解为:

“如果Int(possibleNumber)返回的可选Int包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”

如果转换成功,actualNumber 常量可以在if 语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。在这个例子中,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")
 }
 // 输出 "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 语句外且在语句后才能获取到值,请参考提前退出

隐式解析可选类型
如上所述,可选类型暗示了常量或者变量可以“没有值”可选可以通过if语句来判断是否有值,如果有值的话可以通过可选绑定解析值

有时候在程序架构中, 第一次被赋值之后,可以 确定一个可选类型总会有值。在这种情况下, 每次都要判断解析可选值非常低效 的,因为可以确定它 总会有值。

这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)把想要用可选的类型后面问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型

可选类型被第一次赋值之后就可以确定之后一直有值的时候隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift类的构造过程中,请参考无主引用以及隐式解析可选属性

一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 String隐式解析可选类型 String 之间的区别

 let possibleString: String? = "An optional string."
 let forcedString: String = possibleString! // 需要感叹号来获取值

 let assumedString: String! = "An implicitly unwrapped optional string."
 let implicitString: String = assumedString // 不需要感叹号

你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。

注意:
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。

你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:

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

你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:

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

注意:
如果一个变量之后可能变成nil的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是nil的话,请使用普通可选类型。

错误处理

你可以使用 错误处理(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]作为参数。

抛出,捕捉,以及传播错误会在错误处理章节详细说明。

断言和先决条件

断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为true(真),则代码像往常一样继续执行。如果布尔条件评估结果为false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。

你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。

除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的错误处理不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。

使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。

断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。

使用断言进行调试

你可以调用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 >= 0true 时,即 age 的值非负的时候,代码才会继续执行。如果 age 的值是负数,就像代码中那样,age >= 0false ,断言被触发,终止应用。

如果不需要断言信息,可以就像这样忽略掉:

 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(假),但是继续执行代码要求条件必须为 true(真)的时候,需要使用先决条件。 例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。

你可以使用全局precondition(_:_:file:line:)函数来写一个先决条件。向这个函数传入一个结果为 true 或者false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:

 // 在一个下标的实现里...
 precondition(index > 0, "Index must be greater than zero.")

你可以调用 precondition(_:_:file:line:)方法来表明出现了一个错误,例如,switch进入了 default分支,但是所有的有效值应该被任意一个其他分支(非 default分支)处理。

注意:
如果你使用unchecked模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为true(真),他将优化你的代码。然而,fatalError(_:file:line:)函数总是中断执行,无论你怎么进行优化设定。

你能使用fatalError(_:file:line:)函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上fatalError("Unimplemented")作为具体实现。因为fatalError不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345