Swift5.3 语言参考(六) 声明

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9740463.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

一个声明引入了一个新的名称或构建到你的程序。例如,您使用声明来引入函数和方法,引入变量和常量,以及定义枚举,结构,类和协议类型。您还可以使用声明来扩展现有命名类型的行为,并将符号导入到其他地方声明的程序中。

在Swift中,大多数声明也是定义,因为它们是在声明它们的同时实现或初始化的。也就是说,由于协议不实现其成员,因此大多数协议成员仅是声明。为方便起见,因为在Swift中区别并不重要,术语声明包括声明和定义。

 1 GRAMMAR OF A DECLARATION
 2 
 3 declaration → import-declaration
 4 
 5 declaration → constant-declaration
 6 
 7 declaration → variable-declaration
 8 
 9 declaration → typealias-declaration
10 
11 declaration → function-declaration
12 
13 declaration → enum-declaration
14 
15 declaration → struct-declaration
16 
17 declaration → class-declaration
18 
19 declaration → protocol-declaration
20 
21 declaration → initializer-declaration
22 
23 declaration → deinitializer-declaration
24 
25 declaration → extension-declaration
26 
27 declaration → subscript-declaration
28 
29 declaration → operator-declaration
30 
31 declaration → precedence-group-declaration
32 
33 declarations → declaration declarations opt

顶级代码

Swift源文件中的顶级代码由零个或多个语句,声明和表达式组成。默认情况下,属于同一模块的每个源文件中的代码都可以访问在源文件顶层声明的变量,常量和其他命名声明。您可以通过使用访问级别修饰符标记声明来覆盖此默认行为,如访问控制级别中所述。

1 GRAMMAR OF A TOP-LEVEL DECLARATION
2 
3 top-level-declaration → statements opt

代码块

甲码块是由各种声明和控制结构,以组语句一起使用。它具有以下形式:

1 {
2     statements
3 }

代码块中的语句包括声明,表达式和其他类型的语句,并按照它们在源代码中的出现顺序执行。

1 GRAMMAR OF A CODE BLOCK
2 
3 code-block → { statements opt }

引用声明

一个引用声明,您可以访问声明当前文件之外的符号。基本表单导入整个模块; 它由import关键字后跟模块名称组成:

import module

提供更多详细信息限制导入的符号 - 您可以在模块或子模块中指定特定子模块或特定声明。使用此详细表单时,只有导入的符号(而不是声明它的模块)在当前作用域中可用。

1 import import kind module.symbol name
2 import module.submodule
1 GRAMMAR OF AN IMPORT DECLARATION
2 
3 import-declaration → attributes opt import import-kind opt import-path
4 
5 import-kind → typealias | struct | class | enum | protocol | let | var | func
6 
7 import-path → import-path-identifier | import-path-identifier . import-path
8 
9 import-path-identifier → identifier | operator

常数声明

一个常数声明引入了一个名为常量的值到你的程序。使用let关键字声明常量声明,并具有以下形式:

let constant name: type = expression

常量声明定义常量名称和初始化表达式值之间的不可变绑定; 设置常量值后,不能更改。也就是说,如果使用类对象初始化常量,则对象本身可以更改,但常量名称与其引用的对象之间的绑定不能。

当在全局范围内声明常量时,必须使用值初始化它。当在函数或方法的上下文中发生常量声明时,可以稍后对其进行初始化,只要保证在第一次读取其值之前设置值即可。当在类或结构声明的上下文中发生常量声明时,它被视为常量属性。常量声明不是计算属性,因此没有getter或setter。

如果常量声明的常量名称是元组模式,则元组中每个项的名称将绑定到初始化表达式中的相应值。

let (firstNumber, secondNumber) = (10, 42)

在此示例中,firstNumber是值的命名常量10,并且secondNumber是值的命名常量42。这两个常量现在可以独立使用:

1 print("The first number is (firstNumber).")
2 // Prints "The first number is 10."
3 print("The second number is (secondNumber).")
4 // Prints "The second number is 42."

当可以推断常量名称的类型时,类型注释(: 类型)在常量声明中是可选的,如类型推断中所述

要声明常量类型属性,请使用static声明修饰符标记声明。类型属性在类型属性中讨论。

有关常量的更多信息以及有关何时使用它们的指导,请参阅常量和变量以及存储的属性。

1 GRAMMAR OF A CONSTANT DECLARATION
2 
3 constant-declaration → attributes opt declaration-modifiers opt let pattern-initializer-list
4 
5 pattern-initializer-list → pattern-initializer | pattern-initializer , pattern-initializer-list
6 
7 pattern-initializer → pattern initializer opt
8 
9 initializer → = expression

变量声明

一个变量声明引入了一个名为变量值到你的程序,并使用声明var的关键字。

变量声明有几种形式,用于声明不同类型的命名可变值,包括存储和计算变量和属性,存储变量和属性观察器以及静态变量属性。要使用的适当形式取决于声明变量的范围以及要声明的变量类型。

注意

您还可以在协议声明的上下文中声明属性,如协议属性声明中所述。

您可以通过使用override声明修饰符标记子类的属性声明来覆盖子类中的属性,如覆盖中所述。

存储变量和存储变量属性

以下表单声明存储变量或存储变量属性:

var variable name: type = expression

您可以在全局范围,函数的本地范围或类或结构声明的上下文中定义此形式的变量声明。当在全局范围或函数的局部范围声明此表单的变量声明时,它被称为存储变量。当它在类或结构声明的上下文中声明时,它被称为存储变量属性。

初始化表达式不能出现在协议声明中,但在所有其他上下文中,初始化表达式是可选的。也就是说,如果不存在初始化表达式,则变量声明必须包含显式类型注释(: 类型)。

与常量声明一样,如果变量名是元组模式,则元组中每个项的名称将绑定到初始化表达式中的相应值。

顾名思义,存储变量或存储变量属性的值存储在内存中。

计算变量和计算属性

以下表单声明计算变量或计算属性:

1 var variable name: type {
2     get {
3         statements
4     }
5     set(setter name) {
6         statements
7     }
8 }

您可以在全局范围,函数的本地范围或类,结构,枚举或扩展声明的上下文中定义此形式的变量声明。当在全局范围或函数的局部范围声明此形式的变量声明时,它被称为计算变量。当它在类,结构或扩展声明的上下文中声明时,它被称为计算属性。

getter用于读取值,setter用于写入值。setter子句是可选的,当只需要一个getter时,你可以省略这两个子句,直接返回所请求的值,如Read-Only Computed Properties中所述。但是,如果提供setter子句,则还必须提供getter子句。

该二传手名称和圆括号是可选的。如果提供setter名称,则将其用作setter参数的名称。如果未提供setter名称,则seter的默认参数名称为newValue,如Shorthand Setter Declaration中所述。

与存储的命名值和存储的变量属性不同,计算的命名值或计算属性的值不存储在内存中。

有关更多信息以及查看计算属性的示例,请参阅计算属性。

存储变量观察器和属性观察器

您还可以使用willSetdidSet观察器声明存储的变量或属性。使用观察器声明的存储变量或属性具有以下形式:

1 var variable name: type = expression {
2     willSet(setter name) {
3         statements
4     }
5     didSet(setter name) {
6         statements
7     }
8 }

您可以在全局范围,函数的本地范围或类或结构声明的上下文中定义此形式的变量声明。当在全局范围或函数的局部范围声明此表单的变量声明时,观察器称为存储变量观察器。当在类或结构声明的上下文中声明它时,观察器被称为属性观察器。

您可以将属性观察器添加到任何存储的属性。您还可以通过覆盖子类中的属性将属性观察器添加到任何继承的属性(无论是存储还是计算),如覆盖属性观察器中所述。

初始化表达式在类或结构声明的上下文中是可选的,但在其他地方需要。该类型的注释是可选的,当类型可以从初始化推断表达。

willSetdidSet观察器提供了一种方式来观察(并适当地作出响应)时被设定的变量或属性的值。首次初始化变量或属性时,不会调用观察器。相反,只有在初始化上下文之外设置值时才会调用它们。

一个willSet观察器被称为设置变量或属性的值之前。新值willSet作为常量传递给观察器,因此在willSet子句的实现中不能更改它。在didSet新的值设置观测后立即调用。与willSet观察器相反,变量或属性的旧值将传递给didSet观察器,以防您仍需要访问它。也就是说,如果在其自己的didSetobserver子句中为变量或属性赋值,则分配的新值将替换刚刚设置并传递给willSet观察器的值。

和子句中的setter名称和括号是可选的。如果提供setter名称,它们将用作和观察器的参数名称。如果不提供setter名称,则观察器的默认参数名称为观察器的默认参数名称。willSetdidSetwillSetdidSetwillSetnewValuedidSetoldValue

didSet当您提供willSet子句时,该子句是可选的。同样,willSet当您提供didSet子句时,该子句是可选的。

有关更多信息以及查看如何使用属性观察器的示例,请参阅Property Observers。

变量属性

要声明类型变量属性,请使用static声明修饰符标记声明。类可以使用class声明修饰符标记类型计算属性,而不是允许子类覆盖超类的实现。类型属性在类型属性中讨论。

注意

在类声明中,static关键字与使用classfinal声明修饰符标记声明具有相同的效果。

 1 GRAMMAR OF A VARIABLE DECLARATION
 2 
 3 variable-declaration → variable-declaration-head pattern-initializer-list
 4 
 5 variable-declaration → variable-declaration-head variable-name type-annotation code-block
 6 
 7 variable-declaration → variable-declaration-head variable-name type-annotation getter-setter-block
 8 
 9 variable-declaration → variable-declaration-head variable-name type-annotation getter-setter-keyword-block
10 
11 variable-declaration → variable-declaration-head variable-name initializer willSet-didSet-block
12 
13 variable-declaration → variable-declaration-head variable-name type-annotation initializer opt willSet-didSet-block
14 
15 variable-declaration-head → attributes opt declaration-modifiers opt var
16 
17 variable-name → identifier
18 
19 getter-setter-block → code-block
20 
21 getter-setter-block → { getter-clause setter-clause opt }
22 
23 getter-setter-block → { setter-clause getter-clause }
24 
25 getter-clause → attributes opt mutation-modifier opt get code-block
26 
27 setter-clause → attributes opt mutation-modifier opt set setter-name opt code-block
28 
29 setter-name → ( identifier )
30 
31 getter-setter-keyword-block → { getter-keyword-clause setter-keyword-clause opt }
32 
33 getter-setter-keyword-block → { setter-keyword-clause getter-keyword-clause }
34 
35 getter-keyword-clause → attributes opt mutation-modifier opt get
36 
37 setter-keyword-clause → attributes opt mutation-modifier opt set
38 
39 willSet-didSet-block → { willSet-clause didSet-clause opt }
40 
41 willSet-didSet-block → { didSet-clause willSet-clause opt }
42 
43 willSet-clause → attributes opt willSet setter-name opt code-block
44 
45 didSet-clause → attributes opt didSet setter-name opt code-block

类型别名声明

一个类型别名声明介绍了现有类型的命名别名到你的程序。类型别名声明使用typealias关键字声明,并具有以下形式:

typealias name = existing type

一个类型别名声明后,别名名称可以代替的现有类型无处不在你的程序。在现有类型可以是命名型或复合型。类型别名不会创建新类型; 它们只是允许名称引用现有类型。

类型别名声明可以使用泛型参数为现有泛型类型指定名称。类型别名可以为现有类型的部分或全部通用参数提供具体类型。例如:

1 typealias StringDictionary<Value> = Dictionary<String, Value>
2 
3 // The following dictionaries have the same type.
4 var dictionary1: StringDictionary<Int> = [:]
5 var dictionary2: Dictionary<String, Int> = [:]

当使用泛型参数声明类型别名时,对这些参数的约束必须与现有类型的泛型参数的约束完全匹配。例如:

typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>

由于类型别名和现有类型可以互换使用,因此类型别名不能引入其他通用约束。

类型别名可以通过省略声明中的所有泛型参数来转发现有类型的泛型参数。例如,Diccionario此处声明的类型别名具有相同的通用参数和约束Dictionary

typealias Diccionario = Dictionary

在协议声明中,类型别名可以为经常使用的类型提供更简单,更方便的名称。例如:

1 protocol Sequence {
2     associatedtype Iterator: IteratorProtocol
3     typealias Element = Iterator.Element
4 }
5 
6 func sum<T: Sequence>(_ sequence: T) -> Int where T.Element == Int {
7     // ...
8 }

如果没有此类别别名,则该sum函数必须引用关联的类型T.Iterator.Element而不是T.Element

另请参阅协议关联类型声明。

1 GRAMMAR OF A TYPE ALIAS DECLARATION
2 
3 typealias-declaration → attributes opt access-level-modifier opt typealias typealias-name generic-parameter-clause opt typealias-assignment
4 
5 typealias-name → identifier
6 
7 typealias-assignment → = type

函数声明

一个函数声明引入了一个函数或方法到你的程序。在类,结构,枚举或协议的上下文中声明的函数称为方法。函数声明使用func关键字声明,并具有以下形式:

1 func function name(parameters) -> return type {
2     statements
3 }

如果函数的返回类型为Void,则返回类型可以省略,如下所示:

1 func function name(parameters) {
2     statements
3 }

必须包含每个参数的类型 - 无法推断。如果inout在参数类型前面写入,则可以在函数范围内修改参数。输入输出参数将在下面的输入输出参数中详细讨论。

函数可以使用元组类型作为函数的返回类型返回多个值。

函数定义可以出现在另一个函数声明中。这种函数称为嵌套函数。

如果嵌套函数捕获保证永不转义的值(例如输入输出参数)或作为非转义函数参数传递,则嵌套函数是非转义的。否则,嵌套函数是一个转义函数。

有关嵌套函数的讨论,请参阅嵌套函数。

参数名称

函数参数是以逗号分隔的列表,其中每个参数都具有多种形式之一。函数调用中参数的顺序必须与函数声明中的参数顺序相匹配。参数列表中最简单的条目具有以下形式:

parameter name: parameter type

参数具有在函数体中使用的名称,以及在调用函数或方法时使用的参数标签。默认情况下,参数名称也用作参数标签。例如:

1 func f(x: Int, y: Int) -> Int { return x + y }
2 f(x: 1, y: 2) // both x and y are labeled

您可以使用以下格式之一覆盖参数标签的默认行为:

1 argument label parameter name: parameter type
2 _ parameter name: parameter type

参数名称前面的名称为参数提供显式参数标签,该标签可以与参数名称不同。相应的参数必须在函数或方法调用中使用给定的参数标签。

_参数名称前面的下划线()会抑制参数标签。相应的参数在函数或方法调用中必须没有标签。

1 func repeatGreeting(_ greeting: String, count n: Int) { /* Greet n times */ }
2 repeatGreeting("Hello, world!", count: 2) //  count is labeled, greeting is not

输入输出参数

输入输出参数传递如下:

  1. 调用该函数时,将复制参数的值。
  2. 在函数体中,修改了副本。
  3. 函数返回时,副本的值将分配给原始参数。

此行为称为copy-in copy-out或按值调用结果。例如,当计算属性或具有观察器的属性作为输入输出参数传递时,其getter作为函数调用的一部分被调用,其setter作为函数返回的一部分被调用。

作为优化,当参数是存储在存储器中的物理地址处的值时,在函数体内部和外部使用相同的存储器位置。优化的行为称为引用调用 ; 它满足了拷入式拷贝模型的所有要求,同时消除了复制的开销。使用copy-in copy-out给出的模型编写代码,而不依赖于逐个引用的优化,以便在有或没有优化的情况下它的行为正确。

在函数内,不要访问作为输入输出参数传递的值,即使原始值在当前范围内可用。访问原始版本是同时访问该值,这违反了Swift的内存独占性保证。出于同样的原因,您无法将相同的值传递给多个输入输出参数。

有关内存安全性和内存独占性的更多信息,请参阅内存安全性。

捕获输入输出参数的闭包或嵌套函数必须是非溢出的。如果您需要捕获in-out参数而不进行变更或观察其他代码所做的更改,请使用捕获列表以不可变的方式显式捕获参数。

1 func someFunction(a: inout Int) -> () -> Int {
2     return { [a] in return a + 1 }
3 }

如果需要捕获并改变输入输出参数,请使用显式本地副本,例如在多线程代码中确保在函数返回之前完成所有变异。

1 func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
2     // Make a local copy and manually copy it back.
3     var localX = x
4     defer { x = localX }
5 
6     // Operate on localX asynchronously, then wait before returning.
7     queue.async { someMutatingOperation(&localX) }
8     queue.sync {}
9 }

有关输入输出参数的更多讨论和示例,请参阅输入输出参数。

特殊参数

可以忽略参数,获取可变数量的值,并使用以下形式提供默认值:

1 _ : parameter type
2 parameter name: parameter type...
3 parameter name: parameter type = default argument value

下划线(_)参数被明确忽略,无法在函数体内访问。

具有基本类型名称后面紧跟三个点(...)的参数被理解为可变参数。函数最多只能有一个可变参数。可变参数被视为包含基本类型名称元素的数组。例如,可变参数Int...被视为[Int]。有关使用可变参数的示例,请参阅可变参数。

具有等号(=)的参数和其类型之后的表达式被理解为具有给定表达式的默认值。调用函数时会计算给定的表达式。如果在调用函数时省略该参数,则使用默认值。

1 func f(x: Int = 42) -> Int { return x }
2 f()       // Valid, uses default value
3 f(x: 7)   // Valid, uses the value provided
4 f(7)      // Invalid, missing argument label

特殊方法

枚举或修改的结构上的方法self必须使用mutating声明修饰符进行标记。

必须使用override声明修饰符标记覆盖超类方法的方法。在没有override修饰符的情况下覆盖方法或override在不覆盖超类方法的方法上使用修饰符是编译时错误。

与类型而不是类型实例关联的方法必须使用static枚举和结构的声明修饰符标记,或使用类的staticclass声明修饰符标记。用class声明修饰符标记的类类型方法可以被子类实现覆盖; 标记为的类类型方法static无法重写。

投掷功能和方法

必须使用throws关键字标记可以抛出错误的函数和方法。这些函数和方法称为抛出函数和抛出方法。它们具有以下形式:

1 func function name(parameters) throws -> return type {
2     statements
3 }

对throw函数或方法的调用必须包装在一个trytry!表达式中(即在一个trytry!运算符的范围内)。

throws关键字是一个函数的类型的一部分,和nonthrowing函数是投掷功能的亚型。因此,您可以在与抛出函数相同的位置使用非抛出函数。

您不能仅基于函数是否可以引发错误来重载函数。也就是说,您可以根据函数参数是否可以引发错误来重载函数。

投掷方法不能覆盖非投掷方法,投掷方法不能满足非投掷方法的协议要求。也就是说,非投掷方法可以覆盖投掷方法,非投掷方法可以满足投掷方法的协议要求。

重新定义函数和方法

可以使用rethrows关键字声明函数或方法,以指示仅当其中一个函数参数引发错误时才会抛出错误。这些函数和方法称为rethrowing函数和rethrowing方法。重新抛出函数和方法必须至少有一个抛出函数参数。

1 func someFunction(callback: () throws -> Void) rethrows {
2     try callback()
3 }

重新抛出函数或方法throw只能在catch子句中包含语句。这允许您在docatch块内调用throw函数并catch通过抛出不同的错误来处理子句中的错误。此外,该catch子句必须只处理其中一个重新抛出函数的抛出参数引发的错误。例如,以下内容无效,因为该catch子句将处理引发的错误alwaysThrows()

 1 func alwaysThrows() throws {
 2     throw SomeError.error
 3 }
 4 func someFunction(callback: () throws -> Void) rethrows {
 5     do {
 6         try callback()
 7         try alwaysThrows()  // Invalid, alwaysThrows() isn't a throwing parameter
 8     } catch {
 9         throw AnotherError.error
10     }
11 }

投掷方法不能覆盖重新抛出方法,投掷方法不能满足重新抛出方法的协议要求。也就是说,重新抛出方法可以覆盖投掷方法,并且重新抛出方法可以满足投掷方法的协议要求。

非返回的功能

Swift定义了一个Never类型,表示函数或方法不返回其调用者。具有Never返回类型的函数和方法称为非返回。非回归函数和方法要么导致不可恢复的错误,要么开始无限期地继续工作。这意味着永远不会执行在调用之后立即运行的代码。抛出和重新抛出函数可以将程序控制转移到适当的catch块,即使它们是非回退的。

可以调用非返回函数或方法来结束elseguard语句的子句,如Guard Statement中所讨论的。

您可以覆盖非返回方法,但新方法必须保留其返回类型和非返回行为。

 1 GRAMMAR OF A FUNCTION DECLARATION
 2 
 3 function-declaration → function-head function-name generic-parameter-clause opt function-signature generic-where-clause opt function-body opt
 4 
 5 function-head → attributes opt declaration-modifiers opt func
 6 
 7 function-name → identifier | operator
 8 
 9 function-signature → parameter-clause throwsopt function-result opt
10 
11 function-signature → parameter-clause rethrows function-result opt
12 
13 function-result → -> attributes opt type
14 
15 function-body → code-block
16 
17 parameter-clause → ( ) | ( parameter-list )
18 
19 parameter-list → parameter | parameter , parameter-list
20 
21 parameter → external-parameter-name opt local-parameter-name type-annotation default-argument-clause opt
22 
23 parameter → external-parameter-name opt local-parameter-name type-annotation
24 
25 parameter → external-parameter-name opt local-parameter-name type-annotation ...
26 
27 external-parameter-name → identifier
28 
29 local-parameter-name → identifier
30 
31 default-argument-clause → = expression

枚举声明

一个枚举声明引入了一个名为枚举类型到你的程序。

枚举声明有两种基本形式,并使用enum关键字声明。使用任一形式声明的枚举体包含零个或多个值 - 称为枚举的情况 - 以及任意数量的声明,包括计算属性,实例方法,类型方法,初始值设定项,类型别名,甚至其他枚举,结构和类声明。枚举声明不能包含deinitializer或协议声明。

枚举类型可以采用任意数量的协议,但不能从类,结构或其他枚举继承。

与类和结构不同,枚举类型没有隐式提供的默认初始值设定项; 必须明确声明所有初始值设定项。初始化程序可以委托给枚举中的其他初始化程序,但只有在初始化程序将其中一个枚举情况分配给初始化程序后,初始化过程才会完成self

与结构类似,但与类不同,枚举是值类型; 枚举的实例在分配给变量或常量时复制,或者作为参数传递给函数调用时复制。有关值类型的信息,请参阅结构和枚举是值类型。

您可以使用扩展声明扩展枚举类型的行为,如扩展声明中所述。

任何类型的枚举个案

以下表单声明了一个枚举类型,其中包含任何类型的枚举个案:

1 enum enumeration name: adopted protocols {
2     case enumeration case 1
3     case enumeration case 2(associated value types)
4 }

以这种形式声明的枚举有时被称为其他编程语言中的歧视联合。

在此表单中,每个案例块由case关键字后跟一个或多个枚举案例组成,以逗号分隔。每个案例的名称必须是唯一的。每种情况也可以指定它存储给定类型的值。这些类型在关联的值类型元组中指定,紧跟在案例名称后面。

存储关联值的枚举情况可用作创建具有指定关联值的枚举实例的函数。就像函数一样,您可以获得对枚举案例的引用,并在稍后的代码中应用它。

1 enum Number {
2     case integer(Int)
3     case real(Double)
4 }
5 let f = Number.integer
6 // f is a function of type (Int) -> Number
7 
8 // Apply f to create an array of Number instances with integer values
9 let evenInts: [Number] = [0, 2, 4, 6].map(f)

有关更多信息以及查看具有关联值类型的案例示例,请参阅关联值。

具有间接的枚举

枚举可以具有递归结构,也就是说,它们可以具有关联值的案例,这些值是枚举类型本身的实例。但是,枚举类型的实例具有值语义,这意味着它们在内存中具有固定的布局。为了支持递归,编译器必须插入一个间接层。

要为特定枚举情况启用间接寻址,请使用indirect声明修饰符对其进行标记。间接案例必须具有关联值。

1 enum Tree<T> {
2     case empty
3     indirect case node(value: T, left: Tree, right: Tree)
4 }

要为具有关联值的枚举的所有情况启用间接,请使用indirect修饰符标记整个枚举- 当枚举包含许多需要使用indirect修饰符标记的情况时,这很方便。

使用indirect修饰符标记的枚举可以包含具有关联值的案例和不具有关联值的案例的混合。也就是说,它不能包含任何也用indirect修饰符标记的情况。

有关原始价值案件的查点

以下表单声明一个枚举类型,其中包含相同基本类型的枚举个案:

1 enum enumeration name: raw-value type, adopted protocols {
2     case enumeration case 1 = raw value 1
3     case enumeration case 2 = raw value 2
4 }

在此表单中,每个案例块由case关键字组成,后跟一个或多个枚举案例,以逗号分隔。与第一种形式的情况不同,每种情况都有一个基本值,称为原始值,具有相同的基本类型。这些值的类型在raw-value类型中指定,并且必须表示整数,浮点数,字符串或单个字符。特别是,原始值类型必须符合Equatable协议和以下协议之一:ExpressibleByIntegerLiteral对于整数文字,ExpressibleByFloatLiteral对于浮点文字,ExpressibleByStringLiteral对于包含任意数量字符的字符串文字,和/ ExpressibleByUnicodeScalarLiteralExpressibleByExtendedGraphemeClusterLiteral对于仅包含单个字符的字符串文字。每个案例都必须具有唯一名称并分配唯一的原始值。

如果原始值的类型被指定为Int和你不分配一个值,以明确的情况下,它们被隐式分配的值012,等等。每个未分配的类型的情况都Int被隐式分配一个原始值,该值从前一个案例的原始值自动递增。

1 enum ExampleEnum: Int {
2     case a, b, c = 5, d
3 }

在上面的例子中,原始值ExampleEnum.aIS 0和的值ExampleEnum.b1。并且因为ExampleEnum.c显式设置5了值,所以值ExampleEnum.d会自动递增,5因此6

如果将原始值类型指定为String并且您没有显式为案例分配值,则会为每个未分配的案例隐式分配一个字符串,该字符串与该案例的名称具有相同的文本。

1 enum GamePlayMode: String {
2     case cooperative, individual, competitive
3 }

在上面的例子中,原始值GamePlayMode.cooperative"cooperative",原始值GamePlayMode.individual"individual",。和原始值GamePlayMode.competitive"competitive"

具有原始值类型的枚举隐式地符合RawRepresentableSwift标准库中定义的协议。因此,他们有一个rawValue属性和一个带签名的可用初始化程序。您可以使用该属性访问枚举案例的原始值,如。您也可以使用原始值来查找相应的大小写(如果有的话),方法是调用枚举的可用初始化程序,如同,返回一个可选的大小写。有关更多信息以及查看具有原始值类型的案例示例,请参阅原始值init?(rawValue: RawValue)rawValueExampleEnum.b.rawValueExampleEnum(rawValue: 5)

访问枚举案例

要引用枚举类型的大小写,请使用dot(.)语法,如EnumerationType.enumerationCase。如果可以从上下文推断枚举类型,则可以省略它(仍然需要点),如枚举语法和隐式成员表达式中所述。

要检查枚举情况的值,请使用switch语句,如使用Switch语句匹配枚举值中所示。枚举类型与switch语句的案例块中的枚举案例模式进行模式匹配,如枚举案例模式中所述。

 1 GRAMMAR OF AN ENUMERATION DECLARATION
 2 
 3 enum-declaration → attributes opt access-level-modifier opt union-style-enum
 4 
 5 enum-declaration → attributes opt access-level-modifier opt raw-value-style-enum
 6 
 7 union-style-enum → indirectopt enum enum-name generic-parameter-clause opt type-inheritance-clause opt generic-where-clause opt { union-style-enum-members opt }
 8 
 9 union-style-enum-members → union-style-enum-member union-style-enum-members opt
10 
11 union-style-enum-member → declaration | union-style-enum-case-clause | compiler-control-statement
12 
13 union-style-enum-case-clause → attributes opt indirectopt case union-style-enum-case-list
14 
15 union-style-enum-case-list → union-style-enum-case | union-style-enum-case , union-style-enum-case-list
16 
17 union-style-enum-caseenum-case-name tuple-type opt
18 
19 enum-name → identifier
20 
21 enum-case-name → identifier
22 
23 raw-value-style-enumenum enum-name generic-parameter-clause opt type-inheritance-clause generic-where-clause opt { raw-value-style-enum-members }
24 
25 raw-value-style-enum-members → raw-value-style-enum-member raw-value-style-enum-members opt
26 
27 raw-value-style-enum-member → declaration | raw-value-style-enum-case-clause | compiler-control-statement
28 
29 raw-value-style-enum-case-clause → attributes opt case raw-value-style-enum-case-list
30 
31 raw-value-style-enum-case-list → raw-value-style-enum-case | raw-value-style-enum-case , raw-value-style-enum-case-list
32 
33 raw-value-style-enum-caseenum-case-name raw-value-assignment opt
34 
35 raw-value-assignment → = raw-value-literal
36 
37 raw-value-literal → numeric-literal | static-string-literal | boolean-literal

结构声明

一个结构声明引入了一个名为结构类型到你的程序。结构声明使用struct关键字声明,并具有以下形式:

1 struct structure name: adopted protocols {
2     declarations
3 }

结构体包含零个或多个声明。这些声明可以包括存储和计算属性,类型属性,实例方法,类型方法,初始化器,下标,类型别名,甚至其他结构,类和枚举声明。结构声明不能包含deinitializer或协议声明。有关包含各种声明的结构的讨论和几个示例,请参阅结构和类。

结构类型可以采用任意数量的协议,但不能从类,枚举或其他结构继承。

有三种方法可以创建先前声明的结构的实例:

  • 调用结构中声明的一个初始值设定项,如Initializers中所述。
  • 如果未声明初始化程序,请调用结构的成员初始化程序,如结构类型的成员初始化程序中所述。
  • 如果没有初始化的声明,并在结构声明的所有属性被赋予初始值,调用结构的默认初始值,如在默认初始化器。

初始化中描述了初始化结构声明的属性的过程。

可以使用dot(.)语法访问结构实例的属性,如访问属性中所述。

结构是价值类型; 在分配给变量或常量时,或者作为参数传递给函数调用时,将复制结构的实例。有关值类型的信息,请参阅结构和枚举是值类型。

您可以使用扩展声明扩展结构类型的行为,如扩展声明中所述。

 1 GRAMMAR OF A STRUCTURE DECLARATION
 2 
 3 struct-declaration → attributes opt access-level-modifier opt struct struct-name generic-parameter-clause opt type-inheritance-clause opt generic-where-clause opt struct-body
 4 
 5 struct-name → identifier
 6 
 7 struct-body → { struct-members opt }
 8 
 9 struct-members → struct-member struct-members opt
10 
11 struct-member → declaration | compiler-control-statement

类声明

一类声明引入了一个名为类类型到你的程序。类声明使用class关键字声明,并具有以下形式:

1 class class name: superclass, adopted protocols {
2     declarations
3 }

类的主体包含零个或多个声明。这些声明可以包括存储和计算属性,实例方法,类型方法,初始化器,单个取消初始化器,下标,类型别名,甚至其他类,结构和枚举声明。类声明不能包含协议声明。有关包含各种声明的类的讨论和几个示例,请参阅结构和类。

类类型只能从一个父类(超类)继承,但可以采用任意数量的协议。该超后第一次出现的类名和冒号,跟任何通过的议定书。泛型类可以继承自其他泛型和非泛型类,但非泛型类只能从其他非泛型类继承。在冒号后写入通用超类的名称时,必须包含该泛型类的全名,包括其泛型参数子句。

正如初始化程序声明中所讨论的,类可以具有指定和便利初始化程序。类的指定初始值设定项必须初始化所有类的声明属性,并且必须在调用其任何超类的指定初始值设定项之前执行此操作。

类可以覆盖其超类的属性,方法,下标和初始值设定项。必须使用override声明修饰符标记重写的属性,方法,下标和指定的初始值设定项。

要求子类实现超类的初始值设定项,请使用required声明修饰符标记超类的初始值设定项。子类的初始化程序的实现也必须使用required声明修饰符进行标记。

虽然在超类中声明的属性和方法是由当前类继承的,但是当子类满足自动初始化程序继承中描述的条件时,才会继承在超类中声明的指定初始值设定项。Swift类不从通用基类继承。

有两种方法可以创建先前声明的类的实例:

  • 调用类中声明的初始值设定项之一,如Initializers中所述。
  • 如果没有初始化的声明,并在类声明的所有属性被赋予初始值,调用类的默认初始值,如在默认初始化器。

使用dot(.)语法访问类实例的属性,如访问属性中所述

类是引用类型; 当分配给变量或常量时,或者作为参数传递给函数调用时,类的实例被引用而不是复制。有关引用类型的信息,请参阅结构和枚举是值类型。

您可以使用扩展声明扩展类类型的行为,如扩展声明中所述。

 1 GRAMMAR OF A CLASS DECLARATION
 2 
 3 class-declaration → attributes opt access-level-modifier opt finalopt class class-name generic-parameter-clause opt type-inheritance-clause opt generic-where-clause opt class-body
 4 
 5 class-declaration → attributes opt final access-level-modifier opt class class-name generic-parameter-clause opt type-inheritance-clause opt generic-where-clause opt class-body
 6 
 7 class-name → identifier
 8 
 9 class-body → { class-members opt }
10 
11 class-members → class-member class-members opt
12 
13 class-member → declaration | compiler-control-statement

协议声明

一个协议声明引入了一个名为协议类型到你的程序。协议声明使用protocol关键字在全局范围内声明,并具有以下形式:

1 protocol protocol name: inherited protocols {
2     protocol member declarations
3 }

协议主体包含零个或多个协议成员声明,这些声明描述了采用协议的任何类型必须满足的一致性要求。特别是,协议可以声明符合类型必须实现某些属性,方法,初始值设定项和下标。协议还可以声明特殊类型的别名,称为关联类型,可以指定协议的各种声明之间的关系。协议声明不能包含类,结构,枚举或其他协议声明。该协议的成员声明在下面详细讨论。

协议类型可以从任何数量的其他协议继承。当协议类型继承自其他协议时,会聚合来自其他协议的一组要求,并且从当前协议继承的任何类型都必须符合所有这些要求。有关如何使用协议继承的示例,请参阅协议继承。

注意

您还可以使用协议组合类型聚合多个协议的一致性要求,如协议组合类型和协议组合中所述。

您可以通过在该类型的扩展声明中采用协议,将协议一致性添加到先前声明的类型。在扩展中,您必须实现所有采用的协议要求。如果类型已经实现了所有要求,则可以将扩展声明的主体留空。

默认情况下,符合协议的类型必须实现协议中声明的所有属性,方法和下标。也就是说,您可以使用optional声明修饰符标记这些协议成员声明,以指定它们的符合类型的实现是可选的。所述optional改性剂可以仅应用于标有成员objc属性,并仅在标有协议的成员objc属性。因此,只有类类型可以采用并符合包含可选成员要求的协议。有关如何使用optional声明修饰符的更多信息以及有关如何访问可选协议成员的指导 - 例如,当您不确定符合类型是否实现它们时 - 请参阅可选协议要求。

要仅将协议的采用限制为类类型,请在冒号后的继承协议列表中包含该AnyObject协议。例如,以下协议只能由类类型采用:

1 protocol SomeProtocol: AnyObject {
2     /* Protocol members go here */
3 }

从标记有AnyObject要求的协议继承的任何协议同样只能由类类型采用。

注意

如果协议标记有该objc属性,则该AnyObject请求将隐式应用于该协议; 没有必要AnyObject明确标记具有要求的协议。

协议是命名类型,因此它们可以出现在代码中与其他命名类型相同的位置,如Protocols as Types中所述。但是,您无法构造协议的实例,因为协议实际上并不提供它们指定的要求的实现。

您可以使用协议,声明其方法的类或结构的代表应该执行,如在代表团。

 1 GRAMMAR OF A PROTOCOL DECLARATION
 2 
 3 protocol-declaration → attributes opt access-level-modifier opt protocol protocol-name type-inheritance-clause opt generic-where-clause opt protocol-body
 4 
 5 protocol-name → identifier
 6 
 7 protocol-body → { protocol-members opt }
 8 
 9 protocol-members → protocol-member protocol-members opt
10 
11 protocol-member → protocol-member-declaration | compiler-control-statement
12 
13 protocol-member-declaration → protocol-property-declaration
14 
15 protocol-member-declaration → protocol-method-declaration
16 
17 protocol-member-declaration → protocol-initializer-declaration
18 
19 protocol-member-declaration → protocol-subscript-declaration
20 
21 protocol-member-declaration → protocol-associated-type-declaration
22 
23 protocol-member-declaration → typealias-declaration

协议声明

协议声明符合类型必须通过在协议声明的主体中包含协议属性声明来实现属性。协议属性声明具有特殊形式的变量声明:

var property name: type { get set }

与其他协议成员声明一样,这些属性声明仅声明符合协议的类型的getter和setter要求。因此,您不会直接在声明它的协议中实现getter或setter。

getter和setter要求可以通过各种方式的符合类型来满足。如果属性声明包含getset关键字,则符合类型可以使用存储的变量属性或可读和可写的计算属性(即同时实现getter和setter的计算属性)来实现它。但是,该属性声明不能实现为常量属性或只读计算属性。如果属性声明仅包含get关键字,则可以将其实现为任何类型的属性。有关实现协议属性要求的符合类型的示例,请参阅属性要求。

另见变量声明。

1 GRAMMAR OF A PROTOCOL PROPERTY DECLARATION
2 
3 protocol-property-declaration → variable-declaration-head variable-name type-annotation getter-setter-keyword-block

协议方法声明

协议声明符合类型必须通过在协议声明的主体中包含协议方法声明来实现方法。协议方法声明与函数声明具有相同的形式,但有两个例外:它们不包含函数体,并且您不能提供任何默认参数值作为函数声明的一部分。有关实现协议方法要求的符合类型的示例,请参阅方法要求。

要在协议声明中声明类或静态方法要求,请使用static声明修饰符标记方法声明。实现此方法的类使用class修饰符声明方法。实现它的结构必须使用static声明修饰符声明方法。如果要在扩展中实现该方法,class则在扩展类时使用static修饰符,如果要扩展结构,则使用修饰符。

另请参见功能声明。

1 GRAMMAR OF A PROTOCOL METHOD DECLARATION
2 
3 protocol-method-declaration → function-head function-name generic-parameter-clause opt function-signature generic-where-clause opt

协议初始化程序声明

协议声明符合类型必须通过在协议声明的主体中包含协议初始化程序声明来实现初始化程序。协议初始化程序声明与初始化程序声明具有相同的形式,但它们不包括初始化程序的主体。

符合类型可以通过实现不可用的初始化器或init!可用的初始化器来满足不可用的协议初始化器要求。符合类型可以通过实现任何类型的初始化器来满足可用的协议初始化器要求。

当类实现初始化程序以满足协议的初始化程序要求时,required如果类尚未使用final声明修饰符标记,则必须使用声明修饰符标记初始化程序。

另请参见初始化程序声明。

1 GRAMMAR OF A PROTOCOL INITIALIZER DECLARATION
2 
3 protocol-initializer-declaration → initializer-head generic-parameter-clause opt parameter-clause throwsopt generic-where-clause opt
4 
5 protocol-initializer-declaration → initializer-head generic-parameter-clause opt parameter-clause rethrows generic-where-clause opt

协议下标声明

协议声明符合类型必须通过在协议声明的主体中包含协议下标声明来实现下标。协议下标声明具有特殊形式的下标声明:

subscript (parameters) -> return type { get set }

下标声明仅声明符合协议的类型的最小getter和setter实现要求。如果下标声明包含getset关键字,则符合类型必须同时实现getter和setter子句。如果下标声明仅包含get关键字,则符合类型必须至少实现getter子句,并且可以选择实现setter子句。

另见下标声明。

1 GRAMMAR OF A PROTOCOL SUBSCRIPT DECLARATION
2 
3 protocol-subscript-declaration → subscript-head subscript-result generic-where-clause opt getter-setter-keyword-block

关联类型声明

协议使用associatedtype关键字声明关联类型。关联类型为类型提供别名,该类型用作协议声明的一部分。关联类型与泛型参数子句中的类型参数类似,但它们与Self声明它们的协议相关联。在该上下文中,Self指的是符合协议的最终类型。有关更多信息和示例,请参阅关联类型。

您可以where在协议声明中使用generic 子句向从其他协议继承的关联类型添加约束,而无需重新声明关联的类型。例如,SubProtocol下面的声明是等效的:

 1 protocol SomeProtocol {
 2     associatedtype SomeType
 3 }
 4 
 5 protocol SubProtocolA: SomeProtocol {
 6     // This syntax produces a warning.
 7     associatedtype SomeType: Equatable
 8 }
 9 
10 // This syntax is preferred.
11 protocol SubProtocolB: SomeProtocol where SomeType: Equatable { }

另请参见类型别名声明。

1 GRAMMAR OF A PROTOCOL ASSOCIATED TYPE DECLARATION
2 
3 protocol-associated-type-declaration → attributes opt access-level-modifier opt associatedtype typealias-name type-inheritance-clause opt typealias-assignment opt generic-where-clause opt

初始化器声明

一个初始化声明引入了一个初始化的一类,结构或枚举到你的程序。初始化器声明使用init关键字声明,并有两种基本形式。

结构,枚举和类类型可以包含任意数量的初始值设定项,但类初始值设定项的规则和关联行为是不同的。不同于结构和枚举,类有两种初始化的:指定的初始值和方便初始化,如在初始化。

以下表单声明了类的结构,枚举和指定初始值设定项的初始值设定项:

1 init(parameters) {
2     statements
3 }

类的指定初始值设定项直接初始化所有类的属性。它不能调用同一个类的任何其他初始值设定项,如果该类有一个超类,它必须调用一个超类的指定初始值设定项。如果类从其超类继承任何属性,则必须先调用其中一个超类的指定初始值设定项,然后才能在当前类中设置或修改任何这些属性。

指定的初始值设定项只能在类声明的上下文中声明,因此不能使用扩展声明添加到类中。

结构和枚举中的初始化程序可以调用其他声明的初始化程序来委派部分或全部初始化过程。

要为类声明便捷初始值设定项,请使用convenience声明修饰符标记初始化程序声明。

1 convenience init(parameters) {
2     statements
3 }

便捷初始化程序可以将初始化过程委托给另一个便利初始化程序或该类的指定初始化程序之一。也就是说,初始化过程必须以对最终初始化类属性的指定初始化程序的调用结束。便捷初始化程序无法调用超类的初始值设定项。

您可以使用required声明修饰符标记指定和便利初始值设定项,以要求每个子类都实现初始化程序。子类的初始值设定项的实现也必须使用required声明修饰符进行标记。

默认情况下,超类中声明的初始值设定项不会被子类继承。也就是说,如果子类使用默认值初始化其所有存储的属性,并且没有定义自己的任何初始化器,则它继承所有超类的初始值设定项。如果子类重写了所有超类的指定初始值设定项,它将继承超类的便捷初始值设定项。

与方法,属性和下标一样,您需要使用override声明修饰符标记重写的指定初始值设定项。

注意

如果使用required声明修饰符标记初始值设定项,则override在覆盖子类中所需的初始值设定项时,也不会使用修饰符标记初始值设定项。

就像函数和方法一样,初始值设定项可以抛出或重新抛出错误。就像函数和方法一样,在初始化程序的参数之后使用throwsor rethrows关键字来指示适当的行为。

要查看各种类型声明中的初始值设定项示例,请参阅初始化。

可用的初始化程序

甲failable初始化是一个类型初始的产生一个可选实例或初始化被声明的类型的隐式展开可选实例。因此,可以使用可用的初始值设定项nil来指示初始化失败。

要声明一个可生成可选实例的可用初始值设定项,请init在初始化程序声明(init?)中为关键字添加一个问号。要声明一个可生成隐式解包的可选实例的可用初始值设定项,请添加一个感叹号(init!)。下面的示例显示了一个init?可用的初始值设定项,它可以生成结构的可选实例。

 1 struct SomeStruct {
 2     let property: String
 3     // produces an optional instance of 'SomeStruct'
 4     init?(input: String) {
 5         if input.isEmpty {
 6             // discard 'self' and return 'nil'
 7             return nil
 8         }
 9         property = input
10     }
11 }

您可以像调用不可init?用的初始化程序一样调用可用的初始化程序,除非您必须处理结果的可选性。

1 if let actualInstance = SomeStruct(input: "Hello") {
2     // do something with the instance of 'SomeStruct'
3 } else {
4     // initialization of 'SomeStruct' failed and the initializer returned 'nil'
5 }

可执行的初始化程序可以nil在初始化程序主体的实现中的任何时刻返回。

可用的初始化程序可以委托给任何类型的初始化程序。不可用的初始化程序可以委托给另一个不可用的初始化程序或一个init!可用的初始化程序。不可用的初始化程序可以init?通过强制解包超类的初始化程序的结果(例如,通过写入)委托给可用的初始化程序super.init()!

初始化失败通过初始化程序委派传播。具体来说,如果可用的初始化程序委托给失败并返回nil的初始化程序,则委派的初始化程序也会失败并隐式返回nil。如果不可用的初始值设定项委托给init!失败并返回的可用初始值设定项nil,则会引发运行时错误(就好像您使用!运算符来解包具有nil值的可选项)。

可以通过任何类型的指定初始化程序在子类中覆盖可用的指定初始值设定项。不可用的指定初始值设定项只能由不可用的指定初始化程序在子类中重写。

有关更多信息以及有关可用初始化程序的示例,请参阅Failable Initializers。

 1 GRAMMAR OF AN INITIALIZER DECLARATION
 2 
 3 initializer-declaration → initializer-head generic-parameter-clause opt parameter-clause throwsopt generic-where-clause opt initializer-body
 4 
 5 initializer-declaration → initializer-head generic-parameter-clause opt parameter-clause rethrows generic-where-clause opt initializer-body
 6 
 7 initializer-head → attributes opt declaration-modifiers opt init
 8 
 9 initializer-head → attributes opt declaration-modifiers opt init ?
10 
11 initializer-head → attributes opt declaration-modifiers opt init !
12 
13 initializer-body → code-block

deinitializer声明

一个deinitializer声明声明一个类类型deinitializer。Deinitializers没有参数,具有以下形式:

1 deinit {
2     statements
3 }

在取消分配类对象之前,当不再引用类对象时,会自动调用deinitializer。deinitializer只能在类声明的主体中声明 - 但不能在类的扩展中声明 - 每个类最多只能有一个。

子类继承其超类的deinitializer,它在子类对象被释放之前被隐式调用。在继承链中的所有deinitializer完成执行之前,不会释放子类对象。

Deinitializers不是直接调用的。

有关如何在类声明中使用deinitializer的示例,请参阅取消初始化。

1 GRAMMAR OF A DEINITIALIZER DECLARATION
2 
3 deinitializer-declaration → attributes opt deinit code-block

扩展声明

一个扩展声明可以扩展现有类型的行为。扩展声明使用extension关键字声明,并具有以下形式:

1 extension type name where requirements {
2     declarations
3 }

扩展声明的主体包含零个或多个声明。这些声明可以包括计算属性,计算类型属性,实例方法,类型方法,初始化器,下标声明,甚至类,结构和枚举声明。扩展声明不能包含deinitializer或协议声明,存储属性,属性观察器或其他扩展声明。无法标记协议扩展中的声明final。有关包含各种声明的扩展的讨论和几个示例,请参阅扩展。

如果类型名称是类,结构或枚举类型,则扩展名会扩展该类型。如果类型名称是协议类型,则扩展名将扩展符合该协议的所有类型。

扩展泛型类型或具有关联类型的协议的扩展声明可以包括要求。如果扩展类型或符合扩展协议的类型的实例满足要求,则实例将获得声明中指定的行为。

扩展声明可以包含初始化程序声明。也就是说,如果您扩展的类型是在另一个模块中定义的,则初始化程序声明必须委托给已在该模块中定义的初始化程序,以确保该类型的成员已正确初始化。

无法在该类型的扩展中覆盖现有类型的属性,方法和初始值设定项。

扩展声明可以通过指定采用的协议将协议一致性添加到现有的类,结构或枚举类型:

1 extension type name: adopted protocols where requirements {
2     declarations
3 }

扩展声明不能将类继承添加到现有类,因此您只能在类型名称和冒号后指定协议列表。

条件一致性

您可以扩展泛型类型以有条件地符合协议,以便只有满足某些要求时,该类型的实例才符合协议。通过在扩展声明中包含需求,可以向协议添加条件一致性。

在某些通用上下文中不使用重写的要求

在一些通用上下文中,从条件一致性到协议获得行为的类型并不总是使用该协议要求的专门实现。为了说明此行为,以下示例定义了两个协议和一个有条件地符合这两种协议的泛型类型。

 1 protocol Loggable {
 2     func log()
 3 }
 4 extension Loggable {
 5     func log() {
 6         print(self)
 7     }
 8 }
 9 
10 protocol TitledLoggable: Loggable {
11     static var logTitle: String { get }
12 }
13 extension TitledLoggable {
14     func log() {
15         print("(Self.logTitle): (self)")
16     }
17 }
18 
19 struct Pair<T>: CustomStringConvertible {
20     let first: T
21     let second: T
22     var description: String {
23         return "((first), (second))"
24     }
25 }
26 
27 extension Pair: Loggable where T: Loggable { }
28 extension Pair: TitledLoggable where T: TitledLoggable {
29     static var logTitle: String {
30         return "Pair of '(T.logTitle)'"
31     }
32 }
33 
34 extension String: TitledLoggable {
35     static var logTitle: String {
36         return "String"
37     }
38 }

Pair结构符合Loggable并且TitledLoggable无论何时其通用类型符合LoggableTitledLoggable分别符合。在下面的示例中,oneAndTwo是一个实例Pair<String>,符合TitledLoggable因为String符合TitledLoggable。直接log()调用该方法时oneAndTwo,将使用包含标题字符串的专用版本。

1 let oneAndTwo = Pair(first: "one", second: "two")
2 oneAndTwo.log()
3 // Prints "Pair of 'String': (one, two)"

但是,当oneAndTwo在通用上下文中使用或作为Loggable协议的实例使用时,不使用专用版本。Swift log()通过仅咨询Pair需要遵守的最低要求来选择要执行的实现Loggable。因此,Loggable使用协议提供的默认实现。

1 func doSomething<T: Loggable>(with x: T) {
2     x.log()
3 }
4 doSomething(with: oneAndTwo)
5 // Prints "(one, two)"

log()传递给的实例上调用时doSomething(_:),将从记录的字符串中省略自定义标题。

协议一致性不能冗余

具体类型只能符合特定协议一次。Swift将冗余协议一致性标记为错误。在两种情况下,您可能会遇到这种错误。第一种情况是您多次明确地遵循相同的协议,但具有不同的要求。第二种情况是您多次隐式地从同一协议继承。这些情况将在下面的部分中讨论。

解决显式冗余问题

即使扩展的要求是互斥的,具体类型上的多个扩展也不能添加对同一协议的一致性。以下示例演示了此限制。两个扩展声明尝试向Serializable协议添加条件一致性,一个用于具有Int元素的数组,一个用于具有String元素的数组。

 1 protocol Serializable {
 2     func serialize() -> Any
 3 }
 4 
 5 extension Array: Serializable where Element == Int {
 6     func serialize() -> Any {
 7         // implementation
 8     }
 9 }
10 extension Array: Serializable where Element == String {
11     func serialize() -> Any {
12         // implementation
13     }
14 }
15 // Error: redundant conformance of 'Array<Element>' to protocol 'Serializable'

如果需要基于多个具体类型添加条件一致性,请创建一个新类型的协议,并在声明条件一致性时将该协议用作要求。

1 protocol SerializableInArray { }
2 extension Int: SerializableInArray { }
3 extension String: SerializableInArray { }
4 
5 extension Array: Serializable where Element: SerializableInArray {
6     func serialize() -> Any {
7         // implementation
8     }
9 }

解决隐式冗余问题

当具体类型有条件地符合协议时,该类型隐式地符合具有相同要求的任何父协议。

如果您需要一个类型来有条件地遵循从单个父级继承的两个协议,则显式声明与父协议的一致性。这避免了以不同的要求两次隐式地符合父协议。

以下示例显式声明了条件一致性,ArrayLoggable在声明其对两者TitledLoggable和新MarkedLoggable协议的条件一致性时避免冲突。

 1 protocol MarkedLoggable: Loggable {
 2     func markAndLog()
 3 }
 4 
 5 extension MarkedLoggable {
 6     func markAndLog() {
 7         print("----------")
 8         log()
 9     }
10 }
11 
12 extension Array: Loggable where Element: Loggable { }
13 extension Array: TitledLoggable where Element: TitledLoggable {
14     static var logTitle: String {
15         return "Array of '(Element.logTitle)'"
16     }
17 }
18 extension Array: MarkedLoggable where Element: MarkedLoggable { }

如果没有显式声明条件一致性LoggableArray扩展,其他扩展将隐式创建这些声明,从而导致错误:

1 extension Array: Loggable where Element: TitledLoggable { }
2 extension Array: Loggable where Element: MarkedLoggable { }
3 // Error: redundant conformance of 'Array<Element>' to protocol 'Loggable'
1 GRAMMAR OF AN EXTENSION DECLARATION
2 
3 extension-declaration → attributes opt access-level-modifier opt extension type-identifier type-inheritance-clause opt generic-where-clause opt extension-body
4 
5 extension-body → { extension-members opt }
6 
7 extension-members → extension-member extension-members opt
8 
9 extension-member → declaration | compiler-control-statement

下标声明

甲标声明允许你添加下标支持用于特定类型的对象和通常用于访问在一个集合,列表或序列中的元素提供了一个方便的语法。使用subscript关键字声明下标声明,并具有以下形式:

1 subscript (parameters) -> return type {
2     get {
3         statements
4     }
5     set(setter name) {
6         statements
7     }
8 }

下标声明只能出现在类,结构,枚举,扩展或协议声明的上下文中。

所述参数指定用于访问相应的类型的元件在一个下标表达一个或多个索引(例如,i在表达式object[i])。虽然用于访问元素的索引可以是任何类型,但每个参数必须包含类型注释以指定每个索引的类型。该返回类型指定被访问的元件的类型。

与计算属性一样,下标声明支持读取和写入所访问元素的值。getter用于读取值,setter用于写入值。setter子句是可选的,当只需要一个getter时,你可以省略这两个子句并直接返回所请求的值。也就是说,如果你提供了一个setter子句,你还必须提供一个getter子句。

该二传手名称和圆括号是可选的。如果提供setter名称,则将其用作setter参数的名称。如果未提供setter名称,则setter的默认参数名称为value。setter名称的类型必须与返回类型相同。

只要参数或返回类型与您正在重载的参数或返回类型不同,您就可以在声明它的类型中重载下标声明。您还可以覆盖从超类继承的下标声明。执行此操作时,必须使用override声明修饰符标记重写的下标声明。

默认情况下,与函数,方法和初始值设定项不同,下标中使用的参数没有参数标签。但是,您可以使用函数,方法和初始化程序使用的相同语法提供显式参数标签。

您还可以在协议声明的上下文中声明下标,如协议下标声明中所述。

有关下标和查看下标声明示例的更多信息,请参阅下标。

 1 GRAMMAR OF A SUBSCRIPT DECLARATION
 2 
 3 subscript-declaration → subscript-head subscript-result generic-where-clause opt code-block
 4 
 5 subscript-declaration → subscript-head subscript-result generic-where-clause opt getter-setter-block
 6 
 7 subscript-declaration → subscript-head subscript-result generic-where-clause opt getter-setter-keyword-block
 8 
 9 subscript-head → attributes opt declaration-modifiers opt subscript generic-parameter-clause opt parameter-clause
10 
11 subscript-result → -> attributes opt type

运算符声明

一个操作符声明引入了一个新的中缀,前缀或者后缀运营商到您的程序,并使用声明operator的关键字。

您可以声明三种不同固定的运算符:中缀,前缀和后缀。运算符的固定性指定运算符与其操作数的相对位置。

运算符声明有三种基本形式,每种固定方式一种。操作者的固定性通过标记与操作者声明中指定infixprefix或者postfix在之前声明修饰符operator关键字。在每种形式中,运算符的名称只能包含运算符中定义的运算符字符。

以下表单声明了一个新的中缀运算符:

infix operator operator name: precedence group

中缀运算符是在其两个操作数之间写入的二元运算符,例如+表达式中熟悉的加法运算符()。2

中缀运营商可以选择指定优先级组。如果省略运算符的优先级组,则Swift使用默认优先级组DefaultPrecedence,它指定的优先级高于TernaryPrecedence。有关更多信息,请参阅优先组声明。

以下表单声明了一个新的前缀运算符:

prefix operator operator name

甲前缀操作者是其操作数之前立即写入,如前缀逻辑非运算符(一元运算符!中的表达式)!a

前缀运算符声明不指定优先级。前缀运算符是非关联的。

以下表单声明了一个新的后缀运算符:

postfix operator operator name

甲后缀运算符是它的操作数后立即写入一元运算符,如后缀强制解包运算符(!在表达式中)a!

与前缀运算符一样,后缀运算符声明不指定优先级。后缀运算符是非关联的。

声明一个新运算符后,通过声明一个与运算符同名的静态方法来实现它。静态方法是其值的运算符采用作为参数-例如所述类型中的一种的一个成员,即乘以操作者Double通过一个Int被实现为一个静态方法在任一DoubleInt结构。如果要实现前缀或后缀运算符,则还必须使用相应的prefixpostfix声明修饰符标记该方法声明。要查看如何创建和实现新运算符的示例,请参阅自定义运算符。

 1 GRAMMAR OF AN OPERATOR DECLARATION
 2 
 3 operator-declaration → prefix-operator-declaration | postfix-operator-declaration | infix-operator-declaration
 4 
 5 prefix-operator-declaration → prefix operator operator
 6 
 7 postfix-operator-declaration → postfix operator operator
 8 
 9 infix-operator-declaration → infix operator operator infix-operator-group opt
10 
11 infix-operator-group → : precedence-group-name

优先组声明

一个优先级组声明介绍了中缀运算符优先级一个新的分组到你的程序。运算符的优先级指定运算符在没有分组括号的情况下与其操作数绑定的紧密程度。

优先组声明具有以下形式:

1 precedencegroup precedence group name {
2     higherThan: lower group names
3     lowerThan: higher group names
4     associativity: associativity
5     assignment: assignment
6 }

在较低的组名和更高组名称列表指定新的优先级组的关系,以现有的优先级组。所述lowerThan优先级组属性仅可用于指代在当前模块的外部声明优先基团。当两个运算符彼此竞争其操作数时,例如在表达式中,具有较高相对优先级的运算符与其操作数的绑定更紧密。5

注意

使用较低组名称和较高组名称相互关联的优先级组必须适合单个关系层次结构,但它们不必形成线性层次结构。这意味着可以使用具有未定义的相对优先级的优先级组。如果没有分组括号,那么这些优先级组中的运算符不能彼此相邻使用。

Swift定义了许多优先级组,以与标准库提供的运算符一起使用。例如,addition(+)和subttraction(-)运算符属于该AdditionPrecedence组,而multiplication(*)和division(/)运算符属于该MultiplicationPrecedence组。有关Swift标准库提供的优先级组的完整列表,请参阅运算符声明。

运算符的关联性指定在没有分组括号的情况下,具有相同优先级的运算符序列如何组合在一起。通过编写上下文敏感的关键字之一指定的经营者的关联leftright或者none-如果你省略的关联性,则默认为none。左关联组的运算符从左到右。例如,减法运算符(-)是左关联的,因此表达式被分组为并计算为。从右到左为右关联组的运算符,以及以关联性指定的运算符6(4 5) 6-7none根本不联想。具有相同优先级的非关联运算符不能与每个运算符相邻。例如,<运算符具有关联性none,这意味着不是有效的表达式。3

在包含可选链接的操作中使用时,优先级组的分配指定运算符的优先级。设置true为时,相应优先级组中的运算符在可选链接期间使用与标准库中的赋值运算符相同的分组规则。否则,当设置为false或省略时,优先级组中的运算符遵循与不执行赋值的运算符相同的可选链接规则。

 1 GRAMMAR OF A PRECEDENCE GROUP DECLARATION
 2 
 3 precedence-group-declaration → precedencegroup precedence-group-name { precedence-group-attributes opt }
 4 
 5 precedence-group-attributes → precedence-group-attribute precedence-group-attributes opt
 6 
 7 precedence-group-attribute → precedence-group-relation
 8 
 9 precedence-group-attribute → precedence-group-assignment
10 
11 precedence-group-attribute → precedence-group-associativity
12 
13 precedence-group-relation → higherThan : precedence-group-names
14 
15 precedence-group-relation → lowerThan : precedence-group-names
16 
17 precedence-group-assignment → assignment : boolean-literal
18 
19 precedence-group-associativity → associativity : left
20 
21 precedence-group-associativity → associativity : right
22 
23 precedence-group-associativity → associativity : none
24 
25 precedence-group-names → precedence-group-name | precedence-group-name , precedence-group-names
26 
27 precedence-group-name → identifier

声明修饰符

声明修饰符是关键字或上下文相关关键字,用于修改声明的行为或含义。您可以通过在声明的属性(如果有)和引入声明的关键字之间写入适当的关键字或上下文相关关键字来指定声明修饰符。

dynamic

将此修饰符应用于可由Objective-C表示的类的任何成员。使用dynamic修饰符标记成员声明时,始终使用Objective-C运行时动态调度对该成员的访问。编译器从不内联或不虚拟化对该成员的访问。

因为dynamic使用Objective-C运行时调度使用修饰符标记的声明,所以必须使用该objc属性标记它们。

final
将此修饰符应用于类或类的属性,方法或下标成员。它应用于类以指示该类不能被子类化。它应用于类的属性,方法或下标,以指示无法在任何子类中重写类成员。有关如何使用该final属性的示例,请参阅防止覆盖。
lazy
将此修饰符应用于类或结构的存储变量属性,以指示在首次访问该属性时,最多计算并存储属性的初始值一次。有关如何使用lazy修改器的示例,请参阅惰性存储属性。
optional

将此修饰符应用于协议的属性,方法或下标成员,以指示实现这些成员不需要符合类型。

您只能将optional修饰符应用于使用该objc属性标记的协议。因此,只有类类型可以采用并符合包含可选成员要求的协议。有关如何使用optional修饰符的更多信息以及有关如何访问可选协议成员的指导 - 例如,当您不确定符合类型是否实现它们时 - 请参阅可选协议要求。

required
将此修饰符应用于类的指定或便捷初始值设定项,以指示每个子类必须实现该初始值设定项。子类的初始化程序的实现也必须用required修饰符标记。
unowned
将此修饰符应用于存储的变量,常量或存储属性,以指示变量或属性具有对作为其值存储的对象的无主引用。如果在取消分配对象后尝试访问变量或属性,则会引发运行时错误。像弱引用一样,属性或值的类型必须是类类型; 与弱引用不同,该类型是非可选的。有关unowned修饰符的示例和更多信息,请参见无主引用。
unowned(safe)
明确的拼写unowned
unowned(unsafe)
将此修饰符应用于存储的变量,常量或存储属性,以指示变量或属性具有对作为其值存储的对象的无主引用。如果在取消分配对象后尝试访问变量或属性,则将访问该对象所在位置的内存,这是一种内存不安全的操作。像弱引用一样,属性或值的类型必须是类类型; 与弱引用不同,该类型是非可选的。有关unowned修饰符的示例和更多信息,请参见无主引用。
weak
将此修饰符应用于存储的变量或存储的变量属性,以指示变量或属性对作为其值存储的对象具有弱引用。变量或属性的类型必须是可选的类类型。如果在取消分配对象后访问变量或属性,则其值为nil。有关weak修饰符的示例和更多信息,请参阅弱引用。

访问控制级别

Swift提供五个级别的访问控制:开放,公共,内部,文件私有和私有。您可以使用以下访问级别修饰符之一标记声明,以指定声明的访问级别。访问控制进行了详细讨论访问控制。

open
将此修饰符应用于声明,以指示声明可以通过与声明相同的模块中的代码进行访问和子类化。open也可以通过导入包含该声明的模块的模块中的代码来访问和子类化使用访问级别修饰符标记的声明。
public
将此修饰符应用于声明,以指示声明可以通过与声明相同的模块中的代码进行访问和子类化。使用public访问级别修饰符标记的声明也可以由导入包含该声明的模块的模块中的代码访问(但不是子类)。
internal
将此修饰符应用于声明,以指示只能通过与声明相同的模块中的代码访问声明。默认情况下,大多数声明都使用internal访问级别修饰符隐式标记。
fileprivate
将此修饰符应用于声明,以指示只能通过与声明相同的源文件中的代码访问声明。
private
将此修饰符应用于声明,以指示声明只能由声明的直接封闭范围内的代码访问。

出于访问控制的目的,同一文件中相同类型的扩展共享访问控制范围。如果它们扩展的类型也在同一个文件中,则它们共享类型的访问控制范围。可以从扩展访问在类型声明中声明的私有成员,并且可以从其他扩展和类型声明访问在一个扩展中声明的私有成员。

上面的每个访问级别修饰符都可以选择接受单个参数,该参数由set括在括号中的关键字组成(例如private(set))。如果要为变量或下标的setter指定小于或等于变量或下标本身的访问级别的访问级别,请使用此形式的访问级别修饰符,如Getters和Setters中所述。

 1 GRAMMAR OF A DECLARATION MODIFIER
 2 
 3 declaration-modifier → class | convenience | dynamic | final | infix | lazy | optional | override | postfix | prefix | required | static | unowned | unowned ( safe ) | unowned ( unsafe ) | weak
 4 
 5 declaration-modifier → access-level-modifier
 6 
 7 declaration-modifier → mutation-modifier
 8 
 9 declaration-modifiers → declaration-modifier declaration-modifiers opt
10 
11 access-level-modifier → private | private ( set )
12 
13 access-level-modifier → fileprivate | fileprivate ( set )
14 
15 access-level-modifier → internal | internal ( set )
16 
17 access-level-modifier → public | public ( set )
18 
19 access-level-modifier → open | open ( set )
20 
21 mutation-modifier → mutating | nonmutating

 

原文地址:https://www.cnblogs.com/strengthen/p/9740463.html