声明
声明(declaration) 用以向程序里引入新的名字或者结构。举例来说,可以使用声明来引入函数和方法,变量和常量,或者定义新的具有命名的枚举、结构体、类和协议类型。还可以使用声明来扩展一个既有的具有命名的类型的行为,或者在程序里引入在其它地方声明的符号。
在 Swift 中,大多数声明在某种意义上讲也是定义,因为它们在声明时往往伴随着实现或初始化。由于协议并不提供实现,大多数协议成员仅仅只是声明而已。为了方便起见,也是因为这些区别在 Swift 中并不是很重要,“声明”这个术语同时包含了声明和定义两种含义。
声明语法

declaration

声明导入声明
声明常量声明
声明变量声明
声明类型别名声明
声明函数声明
声明枚举声明
声明结构体声明
声明类声明
声明actor 声明
声明协议声明
声明构造器声明
声明析构器声明
声明扩展声明
声明下标声明
声明运算符声明

declarations

多条声明声明 多条声明可选

顶级代码

Swift 的源文件中的顶级代码(top-level code)由零个或多个语句、声明和表达式组成。默认情况下,在一个源文件的顶层声明的变量,常量和其他具有命名的声明可以被同模块中的每一个源文件中的代码访问。可以使用一个访问级别修饰符来标记声明来覆盖这种默认行为,请参阅 访问控制级别
顶级声明语法
顶级声明多条语句可选

代码块

代码块(code block) 可以将一些声明和控制结构体组织在一起。它有如下的形式:
1
{
2
语句
3
}
Copied!
代码块中的“语句”包括声明、表达式和各种其他类型的语句,它们按照在源码中的出现顺序被依次执行。
代码块语法

code-block

代码块{ 多条语句可选 }

导入声明

导入声明(import declaration) 让你可以使用在其他文件中声明的内容。导入语句的基本形式是导入整个模块,它由 import 关键字和紧随其后的模块名组成:
1
import 模块
Copied!
可以对导入操作提供更细致的控制,如指定一个特殊的子模块或者指定一个模块或子模块中的某个声明。提供了这些限制后,在当前作用域中,只有被导入的符号是可用的,而不是整个模块中的所有声明。
1
import 导入类型 模块.符号名
2
import 模块.子模块
Copied!

grammer-of-an-import-declaration

导入声明语法

import-declaration

导入声明特性列表可选 import 导入类型可选 导入路径

import-kind

导入类型typealias | struct | class | enum | protocol | let | var | func

import-path

import-path-identifier

导入路径标识符标识符 | 运算符

常量声明

常量声明(constant declaration) 可以在程序中引入一个具有命名的常量。常量以关键字 let 来声明,遵循如下格式:
1
let 常量名称: 类型 = 表达式
Copied!
常量声明在“常量名称”和用于初始化的“表达式”的值之间定义了一种不可变的绑定关系;当常量的值被设定之后,它就无法被更改。这意味着,如果常量以类对象来初始化,对象本身的内容是可以改变的,但是常量和该对象之间的绑定关系是不能改变的。
当一个常量被声明为全局常量时,它必须拥有一个初始值。在函数或者方法中声明一个常量时,它并不需要拥有一个初始值,只需要保证在第一次对其进行读操作之前为其设置一个值。在类或者结构体中声明一个常量时,它将作为常量属性(constant property)。常量声明不能是计算型属性,因此也没有存取方法。
如果常量名称是元组形式,元组中每一项的名称都会和初始化表达式中对应的值进行绑定。
1
let (firstNumber, secondNumber) = (10, 42)
Copied!
在上例中,firstNumber 是一个值为 10 的常量,secnodeName 是一个值为 42 的常量。所有常量都可以独立地使用:
1
print("The first number is \(firstNumber).")
2
// 打印“The first number is 10.”
3
print("The second number is \(secondNumber).")
4
// 打印“The second number is 42.”
Copied!
当常量名称的类型(: 类型)可以被推断出时,类型注解在常量声明中是可选的,正如 类型推断 中所描述的。
声明一个常量类型属性要使用 static 声明修饰符。类的常量类型属性总是隐式地被标记为 final ;你无法用 classfinal 声明修饰符实现允许或禁止被子类重写的目的。类型属性在 类型属性 中有介绍。
如果还想获得更多关于常量的信息或者想在使用中获得帮助,请参阅 常量和变量存储属性

grammer-of-a-constant-declaration

常量声明语法

constant-declaration

常量声明特性列表可选 声明修饰符列表可选 let 模式构造器列表

pattern-initializer-list

模式构造器列表模式构造器 | 模式构造器 , 模式构造器列表

pattern-initializer

模式构造器模式 构造器可选

initializer

构造器= 表达式

变量声明

变量声明(variable declaration) 可以在程序中引入一个具有命名的变量,它以关键字 var 来声明。
变量声明有几种不同的形式,可以声明不同种类的命名值和可变值,如存储型和计算型变量和属性,属性观察器,以及静态变量属性。所使用的声明形式取决于变量声明的适用范围和打算声明的变量类型。
注意
也可以在协议声明中声明属性,详情请参阅 协议属性声明
可以在子类中重写继承来的变量属性,使用 override 声明修饰符标记属性的声明即可,详情请参阅 重写

存储型变量和存储型变量属性

使用如下形式声明一个存储型变量或存储型变量属性:
1
var 变量名称: 类型 = 表达式
Copied!
可以在全局范围,函数内部,或者在类和结构体的声明中使用这种形式来声明一个变量。当变量以这种形式在全局范围或者函数内部被声明时,它代表一个存储型变量。当它在类或者结构体中被声明时,它代表一个存储型变量属性(stored variable property)
用于初始化的表达式不可以在协议的声明中出现,在其他情况下,该表达式是可选的。如果没有初始化表达式,那么变量声明必须包含类型注解(: type)。
如同常量声明,如果变量名称是元组形式,元组中每一项的名称都会和初始化表达式中对应的值进行绑定。
正如名字所示,存储型变量和存储型变量属性的值会存储在内存中。

计算型变量和计算型属性

使用如下形式声明一个计算型变量或计算型属性:
1
var 变量名称: 类型 {
2
get {
3
语句
4
}
5
set(setter 名称) {
6
语句
7
}
8
}
Copied!
可以在全局范围、函数内部,以及类、结构体、枚举、扩展的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,它表示一个计算型变量。当它在类、结构体、枚举、扩展声明的上下文中被声明时,它表示一个计算型属性(computed property)
getter 用来读取变量值,setter 用来写入变量值。setter 子句是可选的,getter 子句是必须的。不过也可以将这些子句都省略,直接返回请求的值,正如在 只读计算型属性 中描述的那样。但是如果提供了一个 setter 子句,就必须也提供一个 getter 子句。
setter 的圆括号以及 setter 名称是可选的。如果提供了 setter 名称,它就会作为 setter 的参数名称使用。如果不提供 setter 名称,setter 的参数的默认名称为 newValue,正如在 便捷 setter 声明 中描述的那样。
与存储型变量和存储型属性不同,计算型变量和计算型属性的值不存储在内存中。
要获得更多关于计算型属性的信息和例子,请参阅 计算型属性

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

可以在声明存储型变量或属性时提供 willSetdidSet 观察器。一个包含观察器的存储型变量或属性以如下形式声明:
1
var 变量名称: 类型 = 表达式 {
2
willSet(setter 名称) {
3
语句
4
}
5
didSet(setter 名称) {
6
语句
7
}
8
}
Copied!
可以在全局范围、函数内部,或者类、结构体的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,观察器表示一个存储型变量观察器。当它在类和结构体的声明中被声明时,观察器表示一个属性观察器。
可以为任何存储型属性添加观察器。也可以通过重写父类属性的方式为任何继承的属性(无论是存储型还是计算型的)添加观察器 ,正如 重写属性观察器 中所描述的。
用于初始化的表达式在类或者结构的声明中是可选的,但是在其他声明中则是必须的。如果可以从初始化表达式中推断出类型信息,那么可以不提供类型注解。
当变量或属性的值被改变时,willSetdidSet 观察器提供了一种观察方法。观察器会在变量的值被改变时调用,但不会在初始化时被调用。
willSet 观察器只在变量或属性的值被改变之前调用。新的值作为一个常量传入 willSet 观察器,因此不可以在 willSet 中改变它。didSet 观察器在变量或属性的值被改变后立即调用。和 willSet 观察器相反,为了方便获取旧值,旧值会传入 didSet 观察器。这意味着,如果在变量或属性的 didiset 观察器中设置值,设置的新值会取代刚刚在 willSet 观察器中传入的那个值。
willSetdidSet 中,圆括号以及其中的 setter 名称是可选的。如果提供了一个 setter 名称,它就会作为 willSetdidSet 的参数被使用。如果不提供 setter 名称,willSet 观察器的默认参数名为 newValuedidSet 观察器的默认参数名为 oldValue
提供了 willSet 时,didSet 是可选的。同样的,提供了 didSet 时,willSet 则是可选的。
要获得更多信息以及查看如何使用属性观察器的例子,请参阅 属性观察器

类型变量属性

要声明一个类型变量属性,用 static 声明修饰符标记该声明。类可以改用 class 声明修饰符标记类的类型计算型属性从而允许子类重写超类的实现。类型属性在 类型属性 章节有详细讨论。

grammer-of-a-variable-declaration

变量声明语法

variable-declaration

variable-declaration-head

变量声明头特性列表可选 声明修饰符列表可选 var

variable-name

变量名称标识符

getter-setter-block

getter-setter 代码块代码块
getter-setter 代码块{ getter 子句 setter 子句可选 }
getter-setter 代码块{ setter 子句 getter 子句 }

getter-clause

getter 子句特性列表可选 get 代码块

setter-clause

setter 子句特性列表可选 set setter 名称可选 代码块

setter-name

setter 名称( 标识符 )

getter-setter-keyword-block

getter-setter 关键字代码块{ getter 关键字子句 setter 关键字子句可选 }
getter-setter 关键字代码块{ setter 关键字子句 getter 关键字子句 }

getter-keyword-clause

getter 关键字子句特性列表可选 get

setter-keyword-clause

setter 关键字子句特性列表可选 set

willSet-didSet-block

willSet-didSet 代码块{ willSet 子句 didSet 子句可选 }
willSet-didSet 代码块{ didSet 子句 willSet 子句可选 }

willSet-clause

willSet 子句特性列表可选 willSet setter 名称可选 代码块

didSet-clause

didSet 子句特性列表可选 didSet setter 名称可选 代码块

类型别名声明

类型别名(type alias) 声明可以在程序中为一个既有类型声明一个别名。类型别名声明语句使用关键字 typealias 声明,遵循如下的形式:
1
typealias 类型别名 = 现存类型
Copied!
当声明一个类型的别名后,可以在程序的任何地方使用“别名”来代替现有类型。现有类型可以是具有命名的类型或者混合类型。类型别名不产生新的类型,它只是使用别名来引用现有类型。
类型别名声明可以通过泛型参数来给一个现有泛型类型提供名称。类型别名为现有类型的一部分或者全部泛型参数提供具体类型。例如:
1
typealias StringDictionary<Value> = Dictionary<String, Value>
2
3
// 下列两个字典拥有同样的类型
4
var dictionary1: StringDictionary<Int> = [:]
5
var dictionary2: Dictionary<String, Int> = [:]
Copied!
当一个类型别名带着泛型参数一起被声明时,这些参数的约束必须与现有参数的约束完全匹配。例如:
1
typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>
Copied!
因为类型别名可以和现有类型相互交换使用,类型别名不可以引入额外的类型约束。
如果在声明处省略所有泛型参数,一个类型别名可以传递已有类型的所有泛型参数。例如,此处声明的 Diccionario 类型别名拥有和 Dictionary 同样的约束和泛型参数。
1
typealias Diccionario = Dictionary
Copied!
在协议声明中,类型别名可以为那些经常使用的类型提供一个更短更方便的名称,例如:
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
}
Copied!
假如没有类型别名,sum 函数将必须引用关联类型通过 T.Iterator.Element 的形式来替代 T.Element。
另请参阅 协议关联类型声明

grammer-of-a-type-alias-declaration

类型别名声明语法

typealias-declaration

typealias-name

类型别名名称标识符

typealias-assignment

类型别名赋值= 类型

函数声明

使用函数声明(function declaration) 在程序中引入新的函数或者方法。在类、结构体、枚举,或者协议中声明的函数会作为方法。函数声明使用关键字 func,遵循如下的形式:
1
func 函数名称(参数列表) -> 返回类型 {
2
语句
3
}
Copied!
如果函数返回 Void 类型,返回类型可以省略,如下所示:
1
func 函数名称(参数列表) {
2
语句
3
}
Copied!
每个参数的类型都要标明,因为它们不能被推断出来。如果您在某个参数类型前面加上了 inout,那么这个参数就可以在这个函数作用域当中被修改。更多关于 inout 参数的讨论,请参阅 输入输出参数
函数声明中语句只包含一个表达式,可以理解为返回该表达式的值。
函数可以使用元组类型作为返回类型来返回多个值。
函数定义可以出现在另一个函数声明内。这种函数被称作嵌套函数(nested function)
大多数时候,嵌套函数都是可逃逸的函数。仅当一个嵌套函数捕获了某个确保了永不逃逸的值——例如一个输入输出参数——或者传入一个非逃逸函数参数的时候,这个嵌套函数才是非逃逸的。
更多关于嵌套函数的讨论,请参阅 嵌套函数

参数名

函数的参数列表由一个或多个函数参数组成,参数间以逗号分隔。函数调用时的参数顺序必须和函数声明时的参数顺序一致。最简单的参数列表有着如下的形式:
参数名称: 参数类型
每个参数有一个参数名称,这个名称与实参标签一样都可以在函数体内被使用。默认情况下,参数名也会被作为实参标签来使用。例如:
1
func f(x: Int, y: Int) -> Int { return x + y }
2
f(x: 1, y: 2) // 参数 x 和 y 都有标签
Copied!
可以按照如下两种形式之一,重写参数名称的默认行为:
实参标签 参数名称: 参数类型 _ 参数名称: 参数类型
在参数名称前的名称会作为这个参数的显式实参标签,它可以和参数名称不同。在函数或方法调用时,相对应的参数必须使用这个实参标签。
参数名称前的下划线(_)可以去除参数的实参标签。在函数或方法调用时,相对应的参数必须去除标签。
1
func repeatGreeting(_ greeting: String, count n: Int) { /* Greet n times */ }
2
repeatGreeting("Hello, world!", count: 2) // count 有标签, greeting 没有
Copied!

输入输出参数

输入输出参数被传递时遵循如下规则:
  1. 1.
    函数调用时,参数的值被拷贝。
  2. 2.
    函数体内部,拷贝后的值被修改。
  3. 3.
    函数返回后,拷贝后的值被赋值给原参数。
这种行为被称为拷入拷出(copy-in copy-out)值结果调用(call by value result)。例如,当一个计算型属性或者一个具有属性观察器的属性被用作函数的输入输出参数时,其 getter 会在函数调用时被调用,而其 setter 会在函数返回时被调用。
作为一种优化手段,当参数值存储在内存中的物理地址时,在函数体内部和外部均会使用同一内存位置。这种优化行为被称为引用调用(call by reference),它满足了拷入拷出模式的所有要求,且消除了复制带来的开销。在代码中,要规范使用拷入拷出模式,不要依赖于引用调用。
不要使用传递给输入输出参数的值,即使原始值在当前作用域中依然可用。当函数返回时,你对原始值所做的更改会被拷贝的值所覆盖。不要依赖于引用调用的优化机制来试图避免这种覆盖。
不能将同一个值传递给多个输入输出参数,因为这种情况下的拷贝与覆盖行为的顺序是不确定的,因此原始值的最终值也将无法确定。
更多关于内存安全和内存独占权的讨论,请参阅 内存安全
如果一个闭包或者嵌套函数捕获了一个输入输出参数,那么这个闭包或者嵌套函数必须是非逃逸的。如果你需要捕获一个输入输出参数,但并不对其进行修改,那么你可以使用捕获列表来显式地表明这是个不可变捕获。
1
func someFunction(a: inout Int) -> () -> Int {
2
return { [a] in return a + 1 }
3
}
Copied!
如果你需要捕获并修改一个输入输出参数,使用一个显式局部拷贝来进行修改操作,在一些例如多线程的场景中,这样做可以确保函数返回之前所有的修改都已完成。
如果嵌套函数在外层函数返回后才调用,嵌套函数对输入输出参数造成的任何改变将不会影响到原始值。例如:
1
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
2
// 创建一个局部拷贝并在适当时候手动拷贝回去
3
var localX = x
4
defer { x = localX }
5
6
// 并行地操作 localX,然后在函数返回前一直等待
7
queue.async { someMutatingOperation(&localX) }
8
queue.sync {}
9
}
Copied!
关于输入输出参数的详细讨论,请参阅 输入输出参数

特殊参数

参数可以被忽略,数量可以不固定,还可以为其提供默认值,使用形式如下:
1
_ : 参数类型
2
参数名称: 参数类型...
3
参数名称: 参数类型 = 默认参数值
Copied!
以下划线(_)命名的参数会被显式忽略,无法在函数内使用。
一个参数的基本类型名称如果紧跟着三个点(...),会被视为可变参数。紧随在可变参数后的参数必须拥有参数标签。一个函数可以拥有多个可变参数。可变参数会作为包含该参数类型元素的数组处理。举例来讲,可变参数 Int... 会作为 [Int] 来处理。关于使用可变参数的例子,请参阅 可变参数
如果在参数类型后面有一个以等号(=)连接的表达式,该参数会拥有默认值,即给定表达式的值。当函数被调用时,给定的表达式会被求值。如果参数在函数调用时被省略了,就会使用其默认值。
1
func f(x: Int = 42) -> Int { return x }
2
3
f() // 有效,使用默认值
4
f(7) // 有效,提供了值
5
f(x: 7) // 无效,该参数没有外部名称
Copied!

特殊方法

枚举或结构体的方法如果会修改 self,则必须以 mutating 声明修饰符标记。
子类重写超类中的方法必须以 override 声明修饰符标记。重写方法时不使用 override 修饰符,或者被 override 修饰符修饰的方法并未对超类方法构成重写,都会导致编译错误。
枚举或者结构体中的类型方法,要以 static 声明修饰符标记,而对于类中的类型方法,除了使用 static,还可使用 class 声明修饰符标记。类中使用 class 声明修饰的方法可以被子类实现重写;类中使用 class finalstatic 声明修饰的方法不可被重写。

特殊名称方法

一些含有特殊名称的方法允许使用函数调用语法糖。如果一个类型定义了某个此类型的方法,那这些类型的实例对象都可以使用函数调用语法。这些函数调用会被解析为某个具有特殊名称的实例方法调用。
如同 dynamicCallable 中描述的一样,只要定义了 dynamicallyCall(withArguments:) 方法或者 dynamicallyCall(withKeywordArguments:) 方法,一个类、结构体或者枚举类型都支持函数调用语法。同时如下面的描述一样,定义了一个函数调用方法(call-as-function method)也可以达到上述效果。如果一个类型同时定义了一个函数调用方法和使用 dynamicCallable 属性的方法,那么在合适的情况下,编译器会优先使用函数调用方法。
函数调用方法的名称是 callAsFunction(),或者任意一个以 callAsFunction( 开头并跟随着一些已标签化或未标签化的参数——例如 callAsFunction(_:_:)callAsFunction(something:) 都是合法的函数调用方法名称。
如下的函数调用是相同的:
1
struct CallableStruct {
2
var value: Int
3
func callAsFunction(_ number: Int, scale: Int) {
4
print(scale * (number + value))
5
}
6
}
7
let callable = CallableStruct(value: 100)
8
callable(4, scale: 2)
9
callable.callAsFunction(4, scale: 2)
10
// 两次函数调用都会打印 208
Copied!
函数调用方法和使用 dynamicCallable 属性定义的方法在编码到类型系统中的信息量和运行时的动态行为能力上会有些差别。当你定义了一个函数调用方法时,你需要指定参数的数量,以及每个参数的类型和标签。与此不同的是,dynamicCallable 属性定义的方法只需要指定用于承载参数的数组类型。
函数调用方法或 dynamicCallable 属性定义的方法并不允许你在任何上下文中把实例对象作为函数类型来处理。示例代码如下:
1
let someFunction1: (Int, Int) -> Void = callable(_:scale:) // Error
2
let someFunction2: (Int, Int) -> Void = callable.callAsFunction(_:scale:)
Copied!
dynamicmemberlookup 描述的一样,subscript(dynamicMember:) 下标允许成员查找的语法糖。

抛出错误的函数和方法

可以抛出错误的函数或方法必须使用 throws 关键字标记。这类函数和方法被称为抛出函数和抛出方法。它们有着下面的形式:
1
func 函数名称(参数列表) throws -> 返回类型 {
2
语句
3
}
Copied!
抛出函数或抛出方法的调用必须包裹在 try 或者 try! 表达式中(也就是说,在作用域内使用 try 或者 try! 运算符)。
throws 关键字是函数的类型的一部分,非抛出函数是抛出函数的子类型。所以,可以在使用抛出函数的地方使用非抛出函数。
不能仅基于函数能否抛出错误来进行函数重写。也就是说,可以基于函数的函数类型的参数能否抛出错误来进行函数重写。
抛出方法不能重写非抛出方法,而且抛出方法不能满足协议对于非抛出方法的要求。也就是说,非抛出方法可以重写抛出方法,而且非抛出方法可以满足协议对于抛出方法的要求。

重抛错误的函数和方法

函数或方法可以使用 rethrows 关键字来声明,从而表明仅当该函数或方法的一个函数类型的参数抛出错误时,该函数或方法才抛出错误。这类函数和方法被称为重抛函数和重抛方法。重新抛出错误的函数或方法必须至少有一个参数的类型为抛出函数。
1
func someFunction(callback: () throws -> Void) rethrows {
2
try callback()
3
}
Copied!
重抛函数或者方法不能够从自身直接抛出任何错误,这意味着它不能够包含 throw 语句。它只能够传递作为参数的抛出函数所抛出的错误。例如,在 do-catch 语句中调用抛出函数,并在 catch 子句中抛出其它错误都是不允许的。
1
func alwaysThrows() throws {
2
throw SomeError.error
3
}
4
func someFunction(callback: () throws -> Void) rethrows {
5
do {
6
try callback()
7
try alwaysThrows() // 非法, alwaysThrows() 不是一个抛出函数类型的参数
8
} catch {
9
throw AnotherError.error
10
}
11
}
Copied!
抛出方法不能重写重抛方法,而且抛出方法不能满足协议对于重抛方法的要求。也就是说,重抛方法可以重写抛出方法,而且重抛方法可以满足对抛出方法的协议要求。

异步函数和方法

异步运行的函数和方法必须用 async 关键字标记。这些函数和方法称为异步函数和异步方法。它们有着下面的形式:
1
func 函数名称(参数列表) async -> 返回类型 {
2
语句
3
}
Copied!
对异步函数或异步方法的调用必须包含在 await 表达式中,即它们必须在 await 操作符的作用域内。
async 关键字是函数类型中的一部分。同步函数是异步函数的子类型。所以,你可以在使用异步函数的地方,使用同步函数。同步方法可以重写异步方法,且同步方法可以满足对异步方法的协议要求。

永不返回的函数

Swift 定义了 Never 类型,它表示函数或者方法不会返回给它的调用者。Never 返回类型的函数或方法可以称为不归,不归函数、方法要么引发不可恢复的错误,要么永远不停地运作,这会使调用后本应执行得代码就不再执行了。但即使是不归函数、方法,抛错函数和重抛出函数也可以将程序控制转移到合适的 catch 代码块。
不归函数、方法可以在 guard 语句的 else 字句中调用,具体讨论在 Guard 语句
你可以重写一个不归方法,但是新的方法必须保持原有的返回类型和没有返回的行为。

grammer-of-a-function-declaration

函数声明语法

function-declaration

function-head

函数头特性列表可选 声明修饰符列表可选 func

function-name

函数名标识符 | 运算符

function-signature

函数签名参数子句列表 async可选 throws可选 函数结果可选
函数签名参数子句列表 async可选 rethrows 函数结果可选

function-result

函数结果-> 特性列表可选 类型

function-body

函数体代码块

parameter-clause

参数子句( ) | ( 参数列表 )

parameter-list

参数列表参数 | 参数 , 参数列表

parameter

external-parameter-name

外部参数名标识符 | -

local-parameter-name

内部参数名标识符 | -

default-argument-clause

默认参数子句= 表达式

枚举声明

在程序中使用枚举声明(enumeration declaration) 来引入一个枚举类型。
枚举声明有两种基本形式,使用关键字 enum 来声明。枚举声明体包含零个或多个值,称为枚举用例,还可包含任意数量的声明,包括计算型属性、实例方法、类型方法、构造器、类型别名,甚至其他枚举、结构体、类和 actor。枚举声明不能包含析构器或者协议声明。
枚举类型可以采纳任意数量的协议,但是枚举不能从类、结构体和其他枚举继承。
不同于类或者结构体,枚举类型并不隐式提供默认构造器,所有构造器必须显式声明。一个构造器可以委托给枚举中的其他构造器,但是构造过程仅当构造器将一个枚举用例赋值给 self 后才算完成。
和结构体类似但是和类不同,枚举是值类型。枚举实例在被赋值到变量或常量时,或者传递给函数作为参数时会被复制。更多关于值类型的信息,请参阅 结构体和枚举是值类型
可以扩展枚举类型,正如在 扩展声明 中讨论的一样。

任意类型的枚举用例

如下的形式声明了一个包含任意类型枚举用例的枚举变量:
1
enum 枚举名称: 采纳的协议 {
2
case 枚举用例1
3
case 枚举用例2(关联值类型)
4
}
Copied!
这种形式的枚举声明在其他语言中有时被叫做可识别联合。
在这种形式中,每个用例块由关键字 case 开始,后面紧接一个或多个以逗号分隔的枚举用例。每个用例名必须是独一无二的。每个用例也可以指定它所存储的指定类型的值,这些类型在关联值类型的元组中被指定,紧跟用例名之后。
具有关联值的枚举用例可以像函数一样使用,通过指定的关联值创建枚举实例。和真正的函数一样,你可以获取枚举用例的引用,然后在后续代码中调用它。
1
enum Number {
2
case integer(Int)
3
case real(Double)
4
}
5
6
// f 的类型为 (Int) -> Number
7
let f = Number.integer
8
9
// 利用 f 把一个整数数组转成 Number 数组
10
let evenInts: [Number] = [0, 2, 4, 6].map(f)
Copied!
要获得更多关于具有关联值的枚举用例的信息和例子,请参阅 关联值

递归枚举

枚举类型可以具有递归结构,就是说,枚举用例的关联值类型可以是枚举类型自身。然而,枚举类型的实例具有值语义,这意味着它们在内存中有固定布局。为了支持递归,编译器必须插入一个间接层。
要让某个枚举用例支持递归,使用 indirect 声明修饰符标记该用例。
1
enum Tree<T> {
2
case empty
3
indirect case node(value: T, left: Tree, right:Tree)
4
}
Copied!
要让一个枚举类型的所有用例都支持递归,使用 indirect 修饰符标记整个枚举类型,当枚举有多个用例且每个用例都需要使用 indirect 修饰符标记的时候这将非常便利。
indirect 修饰符标记的枚举用例必须有一个关联值。使用 indirect 修饰符标记的枚举类型可以既包含有关联值的用例,同时还可包含没有关联值的用例。但是,它不能再单独使用 indirect 修饰符来标记某个用例。

拥有原始值的枚举用例

以下形式声明了一种枚举类型,其中各个枚举用例的类型均为同一种基本类型:
1
enum 枚举名称: 原始值类型, 采纳的协议 {
2
case 枚举用例1 = 原始值1
3
case 枚举用例2 = 原始值2
4
}
Copied!
在这种形式中,每一个用例块由 case 关键字开始,后面紧跟一个或多个以逗号分隔的枚举用例。和第一种形式的枚举用例不同,这种形式的枚举用例包含一个基础值,叫做原始值,各个枚举用例的原始值的类型必须相同。这些原始值的类型通过原始值类型指定,必须表示一个整数、浮点数、字符串或者字符。原始值类型必须符合 Equatable 协议和下列字面量转换协议中的一种:整型字面量需符合 IntergerLiteralConvertible 协议,浮点型字面量需符合 FloatingPointLiteralConvertible 协议,包含任意数量字符的字符串型字面量需符合 StringLiteralConvertible 协议,仅包含一个单一字符的字符串型字面量需符合 ExtendedGraphemeClusterLiteralConvertible 协议。每一个用例的名字和原始值必须唯一。
如果原始值类型被指定为 Int,则不必为用例显式地指定原始值,它们会隐式地被赋值 012 等。每个未被赋值的 Int 类型的用例会被隐式地赋值,其值为上一个用例的原始值加 1
1
enum ExampleEnum: Int {
2
case a, b, c = 5, d
3
}
Copied!
在上面的例子中,ExampleEnum.A 的原始值是 0ExampleEnum.B 的原始值是 1。因为 ExampleEnum.C 的原始值被显式地设定为 5,因此 ExampleEnum.D 的原始值会自动增长为 6
如果原始值类型被指定为 String 类型,你不用明确地为用例指定原始值,每个没有指定原始值的用例会隐式地将用例名字作为原始值。
1
enum GamePlayMode: String {
2
case cooperative, individual, competitive
3
}
Copied!
在上面这个例子中,GamePlayMode.cooperative 的原始值是 "cooperative"GamePlayMode.individual 的原始值是 "individual"GamePlayMode.competitive 的原始值是 "competitive"
枚举用例具有原始值的枚举类型隐式地符合定义在 Swift 标准库中的 RawRepresentable 协议。所以,它们拥有一个 rawValue 属性和一个可失败构造器 init?(rawValue: RawValue)。可以使用 rawValue 属性去获取枚举用例的原始值,例如 ExampleEnum.b.rawValue。还可以根据原始值来创建一个相对应的枚举用例,只需调用枚举的可失败构造器,例如 ExampleEnum(rawValue: 5),这个可失败构造器返回一个可选类型的用例。要获得更多关于具有原始值的枚举用例的信息和例子,请参阅 原始值

访问枚举用例

使用点语法(.)来引用枚举类型的枚举用例,例如 EnumerationType.enumerationCase。当枚举类型可以由上下文推断而出时,可以省略它(但是 . 仍然需要),正如 枚举语法显式成员表达式 所述。
可以使用 switch 语句来检验枚举用例的值,正如 使用 switch 语句匹配枚举值 所述。枚举类型是模式匹配的,依靠 switch 语句 case 块中的枚举用例模式,正如 枚举用例模式 所述。

grammer-of-an-enumeration-declaration

枚举声明语法

enum-declaration

枚举声明特性列表可选 访问级别修饰符可选 联合风格枚举
联合风格枚举indirect可选 enum 枚举名称 泛型形参子句可选 类型继承子句可选 { 多个联合风格枚举成员可选 }

union-style-enum-members

多个联合风格枚举成员联合风格枚举成员 多个联合风格枚举成员可选

union-style-enum-member

联合风格枚举成员声明 | 联合风格枚举用例子句 | 编译控制流语句

union-style-enum-case-clause

联合风格枚举用例子句特性列表可选 indirect可选 case 联合风格枚举用例列表

union-style-enum-case-list

union-style-enum-case

联合风格枚举用例枚举用例名称 元组类型可选

enum-name

枚举名称标识符

enum-case-name

枚举用例名称标识符

raw-value-style-enum

raw-value-style-enum-members

多个原始值风格枚举成员原始值风格枚举成员 多个原始值风格枚举成员可选

raw-value-style-enum-member

原始值风格枚举成员声明 | 原始值风格枚举用例子句 | 编译控制流语句

raw-value-style-enum-case-clause

原始值风格枚举用例子句特性列表可选 case 原始值风格枚举用例列表

raw-value-style-enum-case-list

raw-value-style-enum-case

原始值风格枚举用例枚举用例名称 原始值赋值可选

raw-value-assignment

原始值赋值= 原始值字面量

raw-value-literal

结构体声明

使用结构体声明(structure declaration) 可以在程序中引入一个结构体类型。结构体声明使用 struct 关键字,遵循如下的形式:
1
struct 结构体名称: 采纳的协议 {
2
多条声明
3
}
Copied!
结构体内可包含零个或多个声明。这些声明可以包括存储型和计算型属性、类型属性、实例方法、类型方法、构造器、下标、类型别名,甚至其他结构体、类、actor 和枚举声明。结构体声明不能包含析构器或者协议声明。关于结构体的详细讨论和示例,请参阅 类和结构体
结构体可以采纳任意数量的协议,但是不能继承自类、枚举或者其他结构体。
有三种方法可以创建一个已声明的结构体实例:
  • 调用结构体内声明的构造器,正如 构造器 所述。
  • 如果没有声明构造器,调用结构体的成员逐一构造器,正如 结构体类型的成员逐一构造器 所述。
  • 如果没有声明构造器,而且结构体的所有属性都有初始值,调用结构体的默认构造器,正如 默认构造器 所述。
结构体的构造过程请参阅 构造过程
结构体实例的属性可以用点语法(.)来访问,正如 访问属性 所述。
结构体是值类型。结构体的实例在被赋予变量或常量,或传递给函数作为参数时会被复制。关于值类型的更多信息,请参阅 结构体和枚举是值类型
可以使用扩展声明来扩展结构体类型的行为,请参阅 扩展声明

grammer-of-a-structure-declaration

结构体声明语法

struct-declaration

struct-name

结构体名称标识符

struct-body

结构体主体{ 多条声明可选 }

struct-name

结构体多个成员结构体成员 结构体多个成员可选

struct-member

结构体成员声明 | 编译控制流语句

类声明

可以在程序中使用类声明(class declaration) 来引入一个类。类声明使用关键字 class,遵循如下的形式:
1
class 类名: 超类, 采纳的协议 {
2
多条声明
3
}
Copied!
类内可以包含零个或多个声明。这些声明可以包括存储型和计算型属性、实例方法、类型方法、构造器、唯一的析构器、下标、类型别名,甚至其他类、结构体、actor 和枚举声明。类声明不能包含协议声明。关于类的详细讨论和示例,请参阅 类和结构体
一个类只能继承自一个超类,但是可以采纳任意数量的协议。超类紧跟在类名和冒号后面,其后跟着采纳的协议。泛型类可以继承自其它泛型类和非泛型类,但是非泛型类只能继承自其它非泛型类。当在冒号后面写泛型超类的名称时,必须写上泛型类的全名,包括它的泛型形参子句。
正如 构造器声明 所讨论的,类可以有指定构造器和便利构造器。类的指定构造器必须初始化类中声明的所有属性,并且必须在调用超类构造器之前。
类可以重写属性、方法、下标以及构造器。重写的属性、方法、下标和指定构造器必须以 override 声明修饰符标记。
为了要求子类去实现超类的构造器,使用 required 声明修饰符标记超类的构造器。子类实现超类构造器时也必须使用 required 声明修饰符。
虽然超类属性和方法声明可以被当前类继承,但是超类声明的指定构造器却不能。即便如此,如果当前类重写了超类的所有指定构造器,它就会继承超类的所有便利构造器。Swift 的类并不继承自一个通用基础类。
有两种方法来创建已声明的类的实例:
  • 调用类中声明的构造器,请参阅 构造器
  • 如果没有声明构造器,而且类的所有属性都被赋予了初始值,调用类的默认构造器,请参阅 默认构造器
类实例属性可以用点语法(.)来访问,请参阅 访问属性
类是引用类型。当被赋予常量或变量,或者传递给函数作为参数时,类的实例会被引用,而不是被复制。关于引用类型的更多信息,请参阅 类是引用类型
可以使用扩展声明来扩展类的行为,请参阅 扩展声明

grammer-of-a-class-declaration

类声明语法

class-declaration

类声明特性列表可选 访问级别修饰符可选 final可选 class 类名 泛型形参子句可选 类型继承子句可选 泛型 where 子句可选 类主体

class-name

类名标识符

class-body

类主体{ 多条声明可选 }
类多个成员类成员 类多个成员可选

class-member

类成员声明 | 编译控制流语句

Actor 声明

可以在程序中使用 actor 声明(actor declaration) 来引入一个 actor。Actor 声明使用关键字 actor,遵循如下的形式:
1
actor 名称: 遵循的协议 {
2
多条声明
3
}
Copied!
Actor 内可包含零个或多个声明。这些声明包括存储属性和计算属性、实例方法、类型方法、构造器、唯一的析构器、下标、类型别名,甚至其他类、结构体和枚举声明。关于包含多种声明的 actors 的几种例子,请参考 Actors
Actor 类型可以遵循任意数量的协议,但是不能继承于其他类、枚举、结构体或者其他 actor。但是用 @objc 标记的 actor 隐性地遵循了 NSObjectProtocol 协议,且作为 NSObject 的子类型暴露给 Objective-C 运行时。
有两种方法来创建声明过的 actor:
  • 构造器 中所述,调用 actor 的构造器。
  • 默认构造器 中所述,如果没有定义构造器,调用 actor 的默认构造器,actor 声明的所有属性会有默认值。
默认情况下,actor 里的成员变量(方法)是与 actor 隔离的。某个函数内的代码或者某个属性 getter 的代码是在 actor 中执行的。由于这些代码已经在同一个 actor 中运行,所以 actor 内部的代码可以与之同步地交互。但 actor 外部的代码必须用 await 标记 actor 内部的代码,以表示这段代码正在另一个 actor 中异步执行。关键路径无法引用 actor 中的隔离成员。Actor 隔离的存储属性能以输入输出参数的方式传递给同步函数,而不能传递给异步函数。
Actor 也有非隔离的成员变量(方法),这些成员变量(方法)以 nonisolated 关键字标记。非隔离的成员变量(方法)执行起来像 actor 外部的代码:它无法与 actor 任意一个隔离状态交互,而且使用时也无需用 await 标记。
Actor 的成员变量(方法)只有是异步状态或非隔离状态时, 才能用 @objc 修饰符标记。
初始化 actor 所声明的属性的过程在 构造过程 一章中有说明。
属性访问 中所述,actor 实例的属性可以通过使用点语法(.)的方式来访问到。
Actor 是引用类型;当被赋值给变量或常量,以及作为参数传递给函数调用时,actor 的实例会被引用,而不是被复制。关于引用类型的更多信息,请参阅 类是引用类型
可以使用扩展声明来扩展 actor 类型的行为,请参阅 扩展声明

协议声明

协议声明(protocol declaration) 可以为程序引入一个命名的协议类型。协议声明只能在全局区域使用 protocol 关键字来进行声明,并遵循如下形式:
1
protocol 协议名称: 继承的协议 {
2
协议成员声明
3
}
Copied!
协议的主体包含零个或多个协议成员声明,这些成员描述了任何采纳该协议的类型必须满足的一致性要求。一个协议可以声明采纳者必须实现的某些属性、方法、构造器以及下标。协议也可以声明各种各样的类型别名,叫做关联类型,它可以指定协议的不同声明之间的关系。协议声明不能包括类、结构体、枚举或者其它协议的声明。协议成员声明会在后面进行讨论。
协议类型可以继承自任意数量的其它协议。当一个协议类型继承自其它协议的时候,来自其它协议的所有要求会聚合在一起,而且采纳当前协议的类型必须符合所有的这些要求。关于如何使用协议继承的例子,请参阅 协议继承
注意
也可以使用协议合成类型来聚合多个协议的一致性要求,请参阅 协议合成类型协议合成
可以通过类型的扩展声明来采纳协议,从而为之前声明的类型添加协议一致性。在扩展中,必须实现所有采纳协议的要求。如果该类型已经实现了所有的要求,可以让这个扩展声明的主体留空。
默认地,符合某个协议的类型必须实现所有在协议中声明的属性、方法和下标。即便如此,可以用 optional 声明修饰符标注协议成员声明,以指定它们的实现是可选的。optional 修饰符仅仅可以用于使用 objc 特性标记过的协议。因此,仅仅类类型可以采用并符合包含可选成员要求的协议。更多关于如何使用 optional 声明修饰符的信息,以及如何访问可选协议成员的指导——例如不能确定采纳协议的类型是否实现了它们时——请参阅 可选协议要求
为了限制协议只能被类类型采纳,需要使用 AnyObject 关键字来标记协议,将 AnyObject 关键在写在冒号后面的继承的协议列表的首位。例如,下面的协议只能被类类型采纳:
1
protocol SomeProtocol: AnyObject {
2
/* 这里是协议成员 */
3
}
Copied!
任何继承自标记有 AnyObject 关键字的协议的协议也仅能被类类型采纳。
注意
如果协议已经用 objc 特性标记了,AnyObject 要求就隐式地应用于该协议,无需显式使用 AnyObject 关键字。
协议类型是命名的类型,因此它们可以像其他命名类型一样使用,正如 协议作为类型 所讨论的。然而,不能构造一个协议的实例,因为协议实际上不提供它们指定的要求的实现。
可以使用协议来声明作为代理的类或者结构体应该实现的方法,正如 委托(代理)模式 中所述。

grammer-of-a-protocol-declaration

协议声明语法

protocol-declaration

protocol-name

协议名称标识符

protocol-body

协议主体{ 协议成员声明列表可选 }

protocol-members

协议多个成员协议成员 协议多个成员可选

protocol-member

protocol-member-declaration

协议成员声明协议属性声明
协议成员声明协议方法声明
协议成员声明协议构造器声明
协议成员声明协议下标声明
协议成员声明协议关联类型声明
协议成员声明类型别名声明

协议属性声明

协议可以通过在协议声明主体中引入一个协议属性声明,来声明符合的类型必须实现的属性。协议属性声明有一种特殊的变量声明形式:
1
var 属性名: 类型 { get set }
Copied!
同其它协议成员声明一样,这些属性声明仅仅针对符合该协议的类型声明了 getter 和 setter 要求,你不能在协议中直接实现 getter 和 setter。
符合类型可以通过多种方式满足 getter 和 setter 要求。如果属性声明包含 getset 关键字,符合类型就可以用存储型变量属性或可读可写的计算型属性来满足此要求,但是属性不能以常量属性或只读计算型属性实现。如果属性声明仅仅包含 get 关键字的话,它可以作为任意类型的属性被实现。关于如何实现协议中的属性要求的例子,请参阅 属性要求
协议声明中声明一个类型属性,属性声明语句必须用 static 声明修饰符。当结构体和枚举遵循该协议时,使用 static 关键字修饰,而类遵循该协议时,使用 staticclass 关键字皆可。当结构体,枚举或类添加扩展遵循协议时,和之前扩展用到的关键字保持一致。扩展为类属性提供默认实现时,必须使用 static 关键字修饰。
另请参阅 变量声明

grammer-of-an-import-declaration

协议属性声明语法

protocol-property-declaration

协议方法声明

协议可以通过在协议声明主体中引入一个协议方法声明,来声明符合的类型必须实现的方法。协议方法声明和函数方法声明有着相同的形式,但有两项例外:它们不包括函数体,也不能包含默认参数。关于如何实现协议中的方法要求的例子,请参阅 方法要求
协议声明中声明一个类型方法,方法声明语句必须用 static 声明修饰符。结构体和枚举遵循协议时,必须使用 static 关键字修饰,而类遵循协议时,使用 staticclass 关键字皆可。当结构体,枚举或类添加扩展遵循协议时,和之前扩展用到的关键字保持一致。扩展为类方法提供默认实现时,必须使用 static 关键字修饰。
另请参阅 函数声明

grammer-of-a-protocol-declaration

协议方法声明语法

protocol-method-declaration

协议构造器声明

协议可以通过在协议声明主体中引入一个协议构造器声明,来声明符合的类型必须实现的构造器。协议构造器声明 除了不包含实现主体外,和构造器声明有着相同的形式。
符合类型可以通过实现一个非可失败构造器或者 init! 可失败构造器来满足一个非可失败协议构造器的要求,可以通过实现任意类型的构造器来满足一个可失败协议构造器的要求。
类在实现一个构造器去满足一个协议的构造器要求时,如果这个类还没有用 final 声明修饰符标记,这个构造器必须用 required 声明修饰符标记。
另请参阅 构造器声明

grammer-of-a-protocol-initializer-declaration

协议构造器声明语法

protocol-initializer-declaration

协议构造器声明构造器头 泛型形参子句可选 参数子句 throws可选 泛型 where 子句可选
协议构造器声明构造器头 泛型形参子句可选 参数子句 rethrows 泛型 where 子句可选

协议下标声明

协议可以通过在协议声明主体中引入一个协议下标声明,来声明符合的类型必须实现的下标。协议下标声明有一个特殊的下标声明形式:
1
subscript (参数列表) -> 返回类型 { get set }
Copied!
下标声明只为符合类型声明了 getter 和 setter 要求。如果下标声明包含 getset 关键字,符合类型也必须实现 getter 和 setter 子句。如果下标声明只包含 get 关键字,符合类型必须实现 getter 子句,可以选择是否实现 setter 子句。
协议声明中声明一个静态下标,下标声明语句必须用 static 声明修饰符。当结构体和枚举遵循该协议时,下标声明使用 static 关键字修饰,而类遵循该协议时,使用 staticclass 关键字皆可。当结构体,枚举或类添加扩展遵循协议时,和之前扩展用到的关键字保持一致。扩展为下标声明提供默认实现时,必须使用 static 关键字修饰。
另请参阅 下标声明

grammer-of-a-protocol-subscript-declaration

协议下标声明语法

protocol-subscript-declaration

协议关联类型声明

使用关键字 associatedtype 来声明协议关联类型。关联类型为作为协议声明的一部分,为某种类型提供了一个别名。关联类型和泛型参数子句中的类型参数很相似,但是它们和 Self 一样,用于协议中。Self 指代采纳协议的类型。要获得更多信息和例子,请参阅 关联类型
在协议声明中使用泛型 where 子句来为继承的协议关联类型添加约束,且不需要重新声明关联类型。例如下面代码中的 SubProtocol 声明。
1
protocol SomeProtocol {
2
associatedtype SomeType
3
}
4
5
protocol SubProtocolA: SomeProtocol {
6
// 此类语法会引发警告。
7
associatedtype SomeType: Equatable
8
}
9
10
// 建议使用此语法。
11
protocol SubProtocolB: SomeProtocol where SomeType: Equatable { }
Copied!
另请参阅 类型别名声明

grammer-of-a-protocol-associated-type-declaration

协议关联类型声明语法

protocol-associated-type-declaration

协议关联类型声明特性列表可选 访问级别修饰符可选 associatedtype 类型别名头 类型继承子句可选 类型别名赋值可选 泛型 where 子句可选

构造器声明

构造器声明会为程序中的类、结构体或枚举引入构造器。构造器使用关键字 init 来声明,有两种基本形式。
结构体、枚举、类可以有任意数量的构造器,但是类的构造器具有不同的规则和行为。不同于结构体和枚举,类有两种构造器,即指定构造器和便利构造器,请参阅 构造过程
采用如下形式声明结构体和枚举的构造器,以及类的指定构造器:
1
init(参数列表) {
2
构造语句
3
}
Copied!
类的指定构造器直接将类的所有属性初始化。它不能调用类中的其他构造器,如果类有超类,则必须调用超类的一个指定构造器。如果该类从它的超类继承了属性,必须在调用超类的指定构造器后才能修改这些属性。
指定构造器只能在类声明中声明,不能在扩展声明中声明。
结构体和枚举的构造器可以调用其他已声明的构造器,从而委托其他构造器来进行部分或者全部构造过程。
要为类声明一个便利构造器,用 convenience 声明修饰符来标记构造器声明:
1
convenience init(参数列表) {
2