声明
声明会在程序中引入新的名称或结构。例如,你可以使用声明来引入函数和方法、变量和常量,以及定义枚举、结构体、类和协议类型。你还可以使用声明来扩展现有命名类型的行为,以及将在其他位置声明的符号导入到程序中。
在 Swift 中,大多数声明也是定义,因为它们在声明的同时就被实现或初始化了。然而,由于协议本身并不实现其成员,因此大多数协议成员仅仅是声明。为了方便起见,并且由于在 Swift 中这种区别并不那么重要,"声明"一词同时涵盖了声明和定义。
声明语法
声明 → 导入声明
声明 → 常量声明
声明 → 变量声明
声明 → 类型别名声明
声明 → 函数声明
声明 → 枚举声明
声明 → 结构体声明
声明 → 类声明
声明 → 参与者声明
声明 → 协议声明
声明 → 初始化器声明
声明 → 析构函数声明
声明 → 扩展声明
声明 → 下标声明
声明 → 宏声明
声明 → 运算符声明
声明 → 优先组声明
顶层代码
Swift 源文件中的顶层代码由零个或多个语句、声明和表达式组成。默认情况下,在源文件顶层声明的变量、常量和其他命名声明可供同一模块内所有源文件的代码访问。您可以使用访问级别修饰符标记声明来覆盖此默认行为,如访问控制级别中所述。
顶层代码有两种:顶层声明和可执行顶层代码。顶层声明仅由声明组成,并且允许出现在所有 Swift 源文件中。可执行顶层代码包含语句和表达式,而不仅仅是声明,并且仅允许作为程序的顶层入口点。
您编译为可执行文件的 Swift 代码最多可以包含以下方法之一来标记顶级入口点,无论代码如何组织成文件和模块:main 属性、NSApplicationMain 属性、UIApplicationMain 属性、main.swift 文件或包含顶级可执行代码的文件。
顶级声明的语法
顶级声明 → 语句?
代码块
代码块用于各种声明和控制结构,将语句组合在一起。其形式如下:
{
<#statements#>
}
代码块内的语句包括声明、表达式和其他类型的语句,并按照它们在源代码中出现的顺序执行。
代码块语法
代码块 → { 语句? }
导入声明
导入声明允许访问在当前文件外部声明的符号。其基本形式是导入整个模块;它由 import 关键字和模块名称组成:
import <#module#>
提供更详细的信息可以限制导入哪些符号——您可以指定特定的子模块,或者在模块或子模块内指定特定的声明。使用这种详细的形式时,只有导入的符号(而不是声明它的模块)在当前作用域中可用。
import <#import kind#> <#module#>.<#symbol name#>
import <#module#>.<#submodule#>
导入声明的语法
导入声明 → 属性? import 导入种类? 导入路径
导入类型 → typealias | struct | class | enum | protocol | let | var | func
导入路径 → 标识符 | 标识符 . 导入路径
常量声明
常量声明用于在程序中引入一个常量命名值。常量声明使用 let 关键字,形式如下:
let <#constant name#>: <#type#> = <#expression#>
常量声明定义了常量名称和初始化表达式的值之间的不可变绑定;常量的值一旦设置,就无法更改。也就是说,如果用类对象初始化常量,则该对象本身可以更改,但常量名称与其引用的对象之间的绑定不能更改。
当常量在全局范围内声明时,必须为其初始化一个值。当常量声明出现在函数或方法的上下文中时,它可以稍后初始化,只要保证在第一次读取其值之前设置好值即可。如果编译器能够证明该常量的值从未被读取,则该常量根本不需要被设置值。这种分析称为明确初始化——编译器证明在读取之前确实设置了值。
笔记
确定性初始化无法构建需要领域知识的证明,并且其跨条件跟踪状态的能力也有限。如果您可以确定某个常量始终会设置一个值,但编译器无法证明这一点,请尝试简化设置该值的代码路径,或者改用变量声明。
当常量声明出现在类或结构体声明的上下文中时,它被视为常量属性。常量声明不是计算属性,因此没有 getter 或 setter 方法。
如果常量声明的常量名称是元组模式,则元组中每一项的名称都与初始化表达式中的对应值绑定。
let (firstNumber, secondNumber) = (10, 42)
在此示例中,firstNumber 是值为 10 命名常量,secondNumber 是值为 42 的命名常量。这两个常量现在可以独立使用:
print("The first number is \(firstNumber).")
// Prints "The first number is 10."
print("The second number is \(secondNumber).")
// Prints "The second number is 42."
当可以推断常量名称的类型时,类型注释(: type)在常量声明中是可选的,如类型推断中所述。
要声明常量类型属性,请使用 static 声明修饰符标记该声明。类的常量类型属性始终是隐式 final 的;您不能使用 class 或 final 声明修饰符标记它以允许或禁止子类覆盖它。类型属性在类型属性中进行了讨论。
有关常量的更多信息以及何时使用它们的指导,请参阅常量和变量和存储属性。
常量声明语法
常量声明 → 属性? 声明修饰符? let 模式初始化列表
模式初始化器列表 → 模式初始化器 | 模式初始化器, 模式初始化器列表
模式初始化器 → 模式初始化器?
初始化器 → = 表达式
变量声明
变量声明将一个名为值的变量引入程序中,并使用 var 关键字进行声明。
变量声明有多种形式,用于声明不同类型的命名可变值,包括存储型和计算型变量及属性、存储型变量和属性观察器,以及静态变量属性。使用哪种形式取决于变量声明的作用域以及你打算声明的变量类型。
笔记
您还可以在协议声明的上下文中声明属性,如协议属性声明中所述。
您可以通过使用 override 声明修饰符标记子类的属性声明来覆盖子类中的属性,如覆盖中所述。
存储变量和存储变量属性
以下形式声明存储变量或存储变量属性:
var <#variable name#>: <#type#> = <#expression#>
您可以在全局作用域、函数局部作用域,或者类或结构体声明的上下文中定义这种形式的变量声明。当这种形式的变量声明在全局作用域或函数局部作用域中声明时,它被称为存储变量。当它在类或结构体声明的上下文中声明时,它被称为存储变量属性。
初始化表达式不能出现在协议声明中,但在其他所有情况下,初始化表达式都是可选的。也就是说,如果没有初始化表达式,变量声明必须包含显式类型注解 (: type)。
与常量声明一样,如果变量声明省略了初始化表达式,则该变量必须在首次读取之前设置值。同样,与常量声明一样,如果变量名是元组模式,则元组中每一项的名称都与初始化表达式中的对应值绑定。
顾名思义,存储变量或存储变量属性的值存储在内存中。
计算变量和计算属性
以下形式声明了一个计算变量或计算属性:
var <#variable name#>: <#type#> {
get {
<#statements#>
}
set(<#setter name#>) {
<#statements#>
}
}
您可以在全局作用域、函数的局部作用域,或者在类、结构体、枚举或扩展声明的上下文中定义这种形式的变量声明。当这种形式的变量声明在全局作用域或函数的局部作用域中声明时,它被称为计算变量。当它在类、结构体或扩展声明的上下文中声明时,它被称为计算属性。
getter 用于读取值,setter 用于写入值。setter 子句是可选的,当只需要 getter 时,可以省略这两个子句,直接返回请求的值,如只读计算属性中所述。但是,如果您提供了 setter 子句,则也必须提供 getter 子句。
设置器名称和括号是可选的。如果您提供了设置器名称,它将被用作设置器参数的名称。如果您未提供设置器名称,则设置器的默认参数名称为 newValue,如速记设置器声明中所述。
与存储的命名值和存储的变量属性不同,计算的命名值或计算属性的值不存储在内存中。
有关更多信息和查看计算属性的示例,请参阅计算属性。
存储变量观察器和属性观察器
您还可以使用 willSet 和 didSet 观察器声明存储变量或属性。使用观察器声明的存储变量或属性具有以下形式:
var <#variable name#>: <#type#> = <#expression#> {
willSet(<#setter name#>) {
<#statements#>
}
didSet(<#setter name#>) {
<#statements#>
}
}
您可以在全局范围、函数的局部范围,或者在类或结构体声明的上下文中定义这种形式的变量声明。当这种形式的变量声明在全局范围或函数的局部范围声明时,观察者被称为存储型变量观察者。当这种形式的变量声明在类或结构体声明的上下文中声明时,观察者被称为属性观察者。
你可以为任何存储属性添加属性观察器。你还可以通过在子类中重写属性来为任何继承的属性(无论是存储属性还是计算属性)添加属性观察器,具体说明请参见重写属性观察器。
在类或结构体声明的上下文中,初始化表达式是可选的,但在其他情况下是必需的。当类型可以通过初始化表达式推断出来时,类型注解也是可选的。此表达式会在您第一次读取属性值时进行求值。如果您在未读取属性初始值的情况下覆盖了它,则此表达式会在您第一次写入属性之前进行求值。
willSet 和 didSet 观察器提供了一种观察变量或属性值何时被设置(并做出适当响应)的方法。变量或属性首次初始化时不会调用观察器,而是仅在初始化上下文之外设置值时才会调用。
在设置变量或属性的值之前,将调用 willSet 观察器。新值作为常量传递给 willSet 观察器,因此在 willSet 子句的实现中无法更改。在设置新值之后,将立即调用 didSet 观察器。与 willSet 观察器不同,变量或属性的旧值会传递给 didSet 观察器,以防您仍然需要访问它。也就是说,如果您在其自身的 didSet 观察器子句中为变量或属性赋值,则您赋的新值将替换刚刚设置并传递给 willSet 观察器的值。
willSet 和 didSet 子句中的 setter 名称和括号是可选的。如果您提供了 setter 名称,它们将用作 willSet 和 didSet 观察器的参数名称。如果您未提供 setter 名称,则 willSet 观察器的默认参数名称为 newValue,didSet 观察器的默认参数名称为 oldValue。
当你提供 willSet 子句时,didSet 子句是可选的。同样,当你提供 didSet 子句时,willSet 子句也是可选的。
如果 didSet 观察器的代码主体引用了旧值,则在观察器之前调用 getter,以使旧值可用。否则,将存储新值而不调用超类的 getter。以下示例展示了一个由超类定义并被其子类重写以添加观察器的计算属性。
class Superclass {
private var xValue = 12
var x: Int {
get { print("Getter was called"); return xValue }
set { print("Setter was called"); xValue = newValue }
}
}
// This subclass doesn't refer to oldValue in its observer, so the
// superclass's getter is called only once to print the value.
class New: Superclass {
override var x: Int {
didSet { print("New value \(x)") }
}
}
let new = New()
new.x = 100
// Prints "Setter was called"
// Prints "Getter was called"
// Prints "New value 100"
// This subclass refers to oldValue in its observer, so the superclass's
// getter is called once before the setter, and again to print the value.
class NewAndOld: Superclass {
override var x: Int {
didSet { print("Old value \(oldValue) - new value \(x)") }
}
}
let newAndOld = NewAndOld()
newAndOld.x = 200
// Prints "Getter was called"
// Prints "Setter was called"
// Prints "Getter was called"
// Prints "Old value 12 - new value 200"
有关更多信息以及如何使用属性观察器的示例,请参阅属性观察器。
类型变量属性
要声明类型变量属性,请使用 static 声明修饰符标记声明。类可以使用 class 声明修饰符标记类型计算属性,以允许子类覆盖超类的实现。类型属性在类型属性中进行了讨论。
变量声明语法
变量声明 → 变量声明头模式初始化列表
变量声明 → 变量声明头变量名类型注解代码块
变量声明 → 变量声明头变量名类型注解 getter-setter 块
变量声明 → 变量声明头变量名类型注解 getter-setter 关键字块
变量声明 → 变量声明头变量名初始化器 willSet-didSet 块
变量声明 → 变量声明头变量名类型注解初始化器? willSet-didSet 块
变量声明头 → 属性? 声明修饰符? var
变量名 → 标识符
getter-setter 块 → 代码块
getter-setter 块 → { getter 子句 setter 子句? }
getter-setter 块 → { setter 子句 getter 子句 }
getter 子句 → 属性? 突变修饰符? get 代码块
setter 子句 → 属性? 变异修饰符? set setter 名称? 代码块
setter 名称 → ( 标识符 )
getter-setter 关键字块 → { getter 关键字子句 setter 关键字子句? }
getter-setter 关键字块 → { setter 关键字子句 getter 关键字子句 }
getter 关键字子句 → 属性? 突变修饰符? get
setter 关键字子句 → 属性? 变异修饰符? set
willSet-didSet 块 → { willSet 子句 didSet 子句? }
willSet-didSet 块 → { didSet 子句 willSet 子句? }
willSet 子句 → 属性? willSet setter 名称? 代码块
didSet 子句 → 属性? didSet setter 名称? 代码块
类型别名声明
类型别名声明用于在程序中引入现有类型的命名别名。类型别名声明使用 typealias 关键字声明,其形式如下:
typealias <#name#> = <#existing type#>
声明类型别名后,可以在程序的任何地方使用别名名称代替现有类型。现有类型可以是命名类型或复合类型。类型别名不会创建新类型;它们只是允许名称引用现有类型。
类型别名声明可以使用泛型参数为现有泛型类型命名。类型别名可以为现有类型的部分或全部泛型参数提供具体类型。例如:
typealias StringDictionary<Value> = Dictionary<String, Value>
// The following dictionaries have the same type.
var dictionary1: StringDictionary<Int> = [:]
var dictionary2: Dictionary<String, Int> = [:]
当使用泛型参数声明类型别名时,这些参数的约束必须与现有类型的泛型参数的约束完全匹配。例如:
typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>
由于类型别名和现有类型可以互换使用,因此类型别名不能引入额外的泛型约束。
类型别名可以通过省略声明中的所有泛型参数来转发现有类型的泛型参数。例如,此处声明的 Diccionario 类型别名具有与 Dictionary 相同的泛型参数和约束。
typealias Diccionario = Dictionary
在协议声明中,类型别名可以为常用的类型提供更简短、更方便的名称。例如:
protocol Sequence {
associatedtype Iterator: IteratorProtocol
typealias Element = Iterator.Element
}
func sum<T: Sequence>(_ sequence: T) -> Int where T.Element == Int {
// ...
}
如果没有此类型别名,sum 函数就必须将关联类型引用为 T.Iterator.Element 而不是 T.Element。
另请参阅协议关联类型声明。
类型别名声明的语法
类型别名声明 → 属性? 访问级别修饰符? typealias 别名 类型别名名称泛型参数子句? 类型别名赋值
类型别名 → 标识符
类型别名赋值 → = 类型
函数声明
函数声明用于在程序中引入函数或方法。在类、结构体、枚举或协议上下文中声明的函数被称为方法。函数声明使用 func 关键字,其形式如下:
func <#function name#>(<#parameters#>) -> <#return type#> {
<#statements#>
}
如果函数的返回类型为 Void,则可以省略返回类型,如下所示:
func <#function name#>(<#parameters#>) {
<#statements#>
}
每个参数的类型必须包含——无法推断。如果在参数类型前写 inout,则可以在函数作用域内修改该参数。In-Out 参数将在下文的 In-Out 参数中详细讨论。
如果函数声明的语句仅包含一个表达式,则该函数声明将被理解为返回该表达式的值。仅当表达式的类型和函数的返回类型不是 Void 且不是像 Never 这样没有任何用例的枚举时,才会考虑这种隐式返回语法。
函数可以使用元组类型作为函数的返回类型来返回多个值。
一个函数定义可以出现在另一个函数声明中。这种函数称为嵌套函数。
如果嵌套函数捕获了一个保证永不逃逸的值(例如输入输出参数),或者将其作为非逃逸函数参数传递,则该嵌套函数是非逃逸函数。否则,该嵌套函数就是逃逸函数。
有关嵌套函数的讨论,请参阅嵌套函数。
参数名称
函数参数是一个以逗号分隔的列表,其中每个参数都有几种不同的形式。函数调用中参数的顺序必须与函数声明中参数的顺序一致。参数列表中最简单的条目具有以下形式:
<#parameter name#>: <#parameter type#>
参数具有名称(在函数体中使用)和参数标签(在调用函数或方法时使用)。默认情况下,参数名称也用作参数标签。例如:
func f(x: Int, y: Int) -> Int { return x + y }
f(x: 1, y: 2) // both x and y are labeled
您可以使用以下形式之一覆盖参数标签的默认行为:
<#argument label#> <#parameter name#>: <#parameter type#>
_ <#parameter name#>: <#parameter type#>
参数名称之前的名称为参数提供了显式的参数标签,该标签可以与参数名称不同。相应的参数在函数或方法调用中必须使用给定的参数标签。
参数名称前的下划线 (_) 会隐藏参数标签。在函数或方法调用中,对应的参数必须没有标签。
func repeatGreeting(_ greeting: String, count n: Int) { /* Greet n times */ }
repeatGreeting("Hello, world!", count: 2) // count is labeled, greeting is not
参数修饰符
参数修饰符改变参数传递给函数的方式。
<#argument label#> <#parameter name#>: <#parameter modifier#> <#parameter type#>
要使用参数修饰符,请在参数类型前写入 inout、borrowing 或 consuming。
func someFunction(a: inout A, b: consuming B, c: C) { ... }
输入输出参数
默认情况下,Swift 中的函数参数是按值传递的:函数内部的任何更改对调用者都是不可见的。要改为使用 inout 参数,请使用 inout 参数修饰符。
func someFunction(a: inout Int) {
a += 1
}
当调用包含输入输出参数的函数时,输入输出参数必须以与号 (&) 为前缀,以标记函数调用可以更改参数的值。
var x = 7
someFunction(&x)
print(x) // Prints "8"
输入输出参数传递如下:
- 当调用该函数时,参数的值被复制。
- 在函数主体中,副本被修改。
- 当函数返回时,副本的值被分配给原始参数。
这种行为被称为拷贝入拷贝出或按值结果调用。例如,当计算属性或具有观察者的属性作为输入输出参数传递时,其 getter 会被作为函数调用的一部分调用,而其 setter 则会作为函数返回的一部分调用。
作为一种优化,当参数是存储在内存中物理地址的值时,函数体内部和外部都会使用相同的内存位置。这种优化行为称为"按引用调用";它满足"拷贝入/拷贝出"模型的所有要求,同时消除了复制的开销。使用"拷贝入/拷贝出"模型编写代码,而不依赖于"按引用调用"优化,这样无论是否使用优化,代码都能正常运行。
在函数内部,请勿访问作为输入输出参数传递的值,即使原始值在当前作用域中可用。访问原始值相当于同时访问该值,这违反了内存独占性。
var someValue: Int
func someFunction(a: inout Int) {
a += someValue
}
// Error: This causes a runtime exclusivity violation
someFunction(&someValue)
出于同样的原因,您不能将相同的值传递给多个输入输出参数。
var someValue: Int
func someFunction(a: inout Int, b: inout Int) {
a += b
b += 1
}
// Error: Cannot pass the same value to multiple in-out parameters
someFunction(&someValue, &someValue)
有关内存安全性和内存排他性的更多信息,请参阅内存安全。
捕获输入输出参数的闭包或嵌套函数必须是非逃逸的。如果需要捕获输入输出参数而不改变其值,请使用捕获列表显式地以不可变的方式捕获该参数。
func someFunction(a: inout Int) -> () -> Int {
return { [a] in return a + 1 }
}
如果需要捕获和改变输入输出参数,请使用显式本地副本,例如在多线程代码中确保所有改变在函数返回之前已完成。
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
// Make a local copy and manually copy it back.
var localX = x
defer { x = localX }
// Operate on localX asynchronously, then wait before returning.
queue.async { someMutatingOperation(&localX) }
queue.sync {}
}
有关输入输出参数的更多讨论和示例,请参阅输入输出参数。
借用和使用参数
默认情况下,Swift 使用一组规则自动管理函数调用之间的对象生命周期,并在需要时复制值。默认规则旨在在大多数情况下最大限度地减少开销——如果您需要更具体的控制,可以使用 borrowing 或 consuming 参数修饰符。在这种情况下,请使用 copy 来显式标记复制操作。
无论是否使用默认规则,Swift 都能保证对象生命周期和所有权在所有情况下都得到正确管理。这些参数修饰符仅影响特定使用模式的相对效率,而不会影响正确性。
borrowing 修饰符表示函数不保留参数的值。在这种情况下,调用者保留对象的所有权并负责对象的生命周期。当函数仅短暂使用该对象时,使用 borrowing 可以最大限度地减少开销。
// `isLessThan` does not keep either argument
func isLessThan(lhs: borrowing A, rhs: borrowing A) -> Bool {
...
}
如果函数需要保留参数的值,例如,通过将其存储在全局变量中 - 则可以使用 copy 来明确复制该值。
// As above, but this `isLessThan` also wants to record the smallest value
func isLessThan(lhs: borrowing A, rhs: borrowing A) -> Bool {
if lhs < storedValue {
storedValue = copy lhs
} else if rhs < storedValue {
storedValue = copy rhs
}
return lhs < rhs
}
相反,consuming 参数修饰符表示函数拥有该值的所有权,并承担在函数返回之前存储或销毁该值的责任。
// `store` keeps its argument, so mark it `consuming`
func store(a: consuming A) {
someGlobalVariable = a
}
当调用者在函数调用后不再需要使用对象时,使用 consuming 可以最大限度地减少开销。
// Usually, this is the last thing you do with a value
store(a: value)
如果在函数调用后继续使用可复制对象,则编译器会在函数调用之前自动复制该对象。
// The compiler inserts an implicit copy here
store(a: someValue) // This function consumes someValue
print(someValue) // This uses the copy of someValue
与 inout 不同,调用函数时 borrowing 或 consuming 参数都不需要任何特殊符号:
func someFunction(a: borrowing A, b: consuming B) { ... }
someFunction(a: someA, b: someB)
明确使用 borrowing 或 consuming 表明您希望更严格地控制运行时所有权管理的开销。由于复制可能导致意外的运行时所有权操作,因此除非使用显式 copy 关键字,否则标有以下任一修饰符的参数都无法复制:
func borrowingFunction1(a: borrowing A) {
// Error: Cannot implicitly copy a
// This assignment requires a copy because
// `a` is only borrowed from the caller.
someGlobalVariable = a
}
func borrowingFunction2(a: borrowing A) {
// OK: Explicit copying works
someGlobalVariable = copy a
}
func consumingFunction1(a: consuming A) {
// Error: Cannot implicitly copy a
// This assignment requires a copy because
// of the following `print`
someGlobalVariable = a
print(a)
}
func consumingFunction2(a: consuming A) {
// OK: Explicit copying works regardless
someGlobalVariable = copy a
print(a)
}
func consumingFunction3(a: consuming A) {
// OK: No copy needed here because this is the last use
someGlobalVariable = a
}
特殊类型的参数
参数可以被忽略,采用可变数量的值,并使用以下形式提供默认值:
_ : <#parameter type#>
<#parameter name#>: <#parameter type#>...
<#parameter name#>: <#parameter type#> = <#default argument value#>
下划线(_)参数被明确忽略,并且无法在函数主体内访问。
如果参数的基类型名称后紧跟三个点 (...),则该参数会被理解为可变参数。紧跟可变参数之后的参数必须带有实参标签。一个函数可以包含多个可变参数。可变参数被视为一个包含基类型名称元素的数组。例如,可变参数 Int... 会被理解为 [Int]。有关使用可变参数的示例,请参阅可变参数。
如果参数的类型后带有等号 (=) 和一个表达式,则表示该参数具有给定表达式的默认值。函数调用时,会计算给定表达式的值。如果调用函数时省略了参数,则使用默认值。
func f(x: Int = 42) -> Int { return x }
f() // Valid, uses default value
f(x: 7) // Valid, uses the value provided
f(7) // Invalid, missing argument label
特殊类型的方法
枚举或结构上修改 self 方法必须用 mutating 声明修饰符标记。
重写超类方法的方法必须使用 override 声明修饰符进行标记。如果重写方法时不使用 override 修饰符,或者在未重写超类方法的方法上使用 override 修饰符,则会导致编译时错误。
与类型(而非类型实例)关联的方法必须使用 static 声明修饰符(对于枚举和结构体)进行标记,或者使用 static 或 class 声明修饰符(对于类)进行标记。使用 class 声明修饰符标记的类类型方法可以被子类实现重写;使用 class final 或 static 标记的类类型方法无法被重写。
具有特殊名称的方法
一些具有特殊名称的方法为函数调用语法提供了语法糖。如果某个类型定义了这些方法之一,则可以在函数调用语法中使用该类型的实例。该函数调用被理解为对该实例上某个特殊命名方法的调用。
类、结构体或枚举类型可以通过定义 dynamicallyCall(withArguments:) 方法或 dynamicallyCall(withKeywordArguments:) 方法(如 dynamicCallable 中所述)或定义"作为函数调用"方法来支持函数调用语法,如下所述。如果类型同时定义了"作为函数调用"方法和 dynamicCallable 属性所使用的方法之一,则在两种方法均可使用的情况下,编译器会优先使用"作为函数调用"方法。
调用函数方法的名称是 callAsFunction(),或者另一个以 callAsFunction( 开头并添加标记或未标记参数的名称 - 例如,callAsFunction(:😃 和 callAsFunction(something:) 也是有效的调用函数方法名称。
以下函数调用是等效的:
struct CallableStruct {
var value: Int
func callAsFunction(_ number: Int, scale: Int) {
print(scale * (number + value))
}
}
let callable = CallableStruct(value: 100)
callable(4, scale: 2)
callable.callAsFunction(4, scale: 2)
// Both function calls print 208.
函数式调用方法和 dynamicCallable 属性中的方法在编码到类型系统中的信息量和运行时可能出现的动态行为量之间做出了不同的权衡。声明函数式调用方法时,需要指定参数的数量以及每个参数的类型和标签。dynamicCallable 属性的方法仅指定用于保存参数数组的类型。
定义一个"调用即函数"方法,或者一个 dynamicCallable 属性的方法,不允许你在函数调用表达式以外的任何上下文中将该类型的实例当作函数来使用。例如:
let someFunction1: (Int, Int) -> Void = callable(_:scale:) // Error
let someFunction2: (Int, Int) -> Void = callable.callAsFunction(_:scale:)
subscript(dynamicMember:) 下标为成员查找提供了语法糖,如 dynamicMemberLookup 中所述。
抛出函数和方法
可能抛出错误的函数和方法必须使用 throws 关键字标记。这些函数和方法被称为抛出函数和抛出方法。它们的形式如下:
func <#function name#>(<#parameters#>) throws -> <#return type#> {
<#statements#>
}
抛出特定错误类型的函数具有以下形式:
func <#function name#>(<#parameters#>) throws(<#error type#>) -> <#return type#> {
<#statements#>
}
对抛出函数或方法的调用必须包装在 try 或 try! 表达式中(即,在 try 或 try! 运算符的范围内)。
函数的类型包括它是否可以抛出错误以及抛出的错误类型。这种子类型关系意味着,例如,你可以在预期抛出错误的上下文中使用不抛出错误的函数。有关抛出错误函数类型的更多信息,请参阅函数类型。有关处理具有显式类型的错误的示例,请参阅指定错误类型。
你不能仅仅根据函数是否可能抛出错误来重载函数。也就是说,你可以根据函数参数是否可能抛出错误来重载函数。
抛出方法不能重写非抛出方法,抛出方法也不能满足非抛出方法的协议要求。也就是说,非抛出方法可以重写抛出方法,非抛出方法可以满足抛出方法的协议要求。
重新抛出函数和方法
函数或方法可以使用 rethrows 关键字声明,以指示仅当其函数参数之一抛出错误时,它才会抛出错误。这些函数和方法被称为重新抛出函数和重新抛出方法。重新抛出函数和方法必须至少有一个抛出函数参数。
func someFunction(callback: () throws -> Void) rethrows {
try callback()
}
重新抛出异常的函数或方法只能在 catch 子句中包含 throw 语句。这样,你就可以在 do-catch 语句中调用抛出异常的函数,并在 catch 子句中通过抛出不同的错误来处理错误。此外,catch 子句必须只能处理由重新抛出异常的函数的某个抛出参数所引发的错误。例如,以下代码无效,因为 catch 子句会处理由 alwaysThrows() 引发的错误。
func alwaysThrows() throws {
throw SomeError.error
}
func someFunction(callback: () throws -> Void) rethrows {
do {
try callback()
try alwaysThrows() // Invalid, alwaysThrows() isn't a throwing parameter
} catch {
throw AnotherError.error
}
}
抛出方法不能覆盖重抛方法,抛出方法也不能满足重抛方法的协议要求。也就是说,重抛方法可以覆盖抛出方法,重抛方法可以满足抛出方法的协议要求。
重新抛出错误的另一个方法是在通用代码中抛出特定的错误类型。例如:
func someFunction<E: Error>(callback: () throws(E) -> Void) throws(E) {
try callback()
}
这种传播错误的方法保留了错误的类型信息。然而,与标记函数 rethrows 不同,这种方法并不能阻止函数抛出相同类型的错误。
异步函数和方法
异步运行的函数和方法必须使用 async 关键字标记。这些函数和方法被称为异步函数和异步方法。它们的形式如下:
func <#function name#>(<#parameters#>) async -> <#return type#> {
<#statements#>
}
对异步函数或方法的调用必须包装在 await 表达式中 - 也就是说,它们必须在 await 运算符的范围内。
async 关键字是函数类型的一部分,而同步函数是异步函数的子类型。因此,你可以在需要异步函数的上下文中使用同步函数。例如,你可以用同步方法重写异步方法,而同步方法也可以满足需要异步方法的协议要求。
您可以根据函数是否为异步函数来重载该函数。在调用处,上下文决定使用哪种重载:在异步上下文中,使用异步函数;在同步上下文中,使用同步函数。
异步方法不能重写同步方法,异步方法也不能满足同步方法的协议要求。也就是说,同步方法可以重写异步方法,同步方法可以满足异步方法的协议要求。
永不返回的函数
Swift 定义了 Never 类型,表示函数或方法不会返回给其调用者。具有 Never 返回类型的函数和方法被称为非返回函数。非返回函数和方法要么会导致不可恢复的错误,要么会启动一系列无限期持续的工作。这意味着,原本会在调用后立即运行的代码永远不会被执行。抛出和重新抛出异常的函数可以将程序控制权转移到合适的 catch 代码块,即使它们是非返回函数。
可以调用不返回函数或方法来结束 guard 语句的 else 子句,如 Guard 语句中所述。
您可以覆盖非返回方法,但新方法必须保留其返回类型和非返回行为。
函数声明的语法
函数声明 → 函数头函数名泛型参数子句? 函数签名泛型 where 子句? 函数体?
函数头 → 属性? 声明修饰符? func
函数名称 → 标识符 | 运算符
函数签名 → 参数子句 async? throws 子句? 函数结果?
函数签名 → 参数子句 async? rethrows 函数结果?
函数结果 → -> 属性? 类型
函数体 → 代码块
参数子句 → ( ) | ( 参数列表 )
参数列表 → 参数 | 参数, 参数列表
参数 → 外部参数名称? 本地参数名称参数类型注释默认参数子句?
参数 → 外部参数名称? 本地参数名称参数类型注解
参数 → 外部参数名称? 本地参数名称参数类型注释...
外部参数名称 → 标识符
本地参数名称 → 标识符
参数类型注解 → : 属性? 参数修饰符? 类型
参数修饰符 → inout | borrowing | consuming
默认参数子句 → = 表达式
枚举声明
枚举声明将命名的枚举类型引入到您的程序中。
枚举声明有两种基本形式,均使用 enum
关键字声明。使用任一形式声明的枚举主体包含零个或多个值(称为枚举用例)以及任意数量的声明,包括计算属性、实例方法、类型方法、构造器、类型别名,甚至其他枚举、结构体、类和参与者声明。枚举声明不能包含析构器或协议声明。
枚举类型可以采用任意数量的协议,但不能从类、结构或其他枚举继承。
与类和结构体不同,枚举类型没有隐式提供的默认构造器;所有构造器都必须显式声明。构造器可以委托给枚举中的其他构造器,但只有在构造器将枚举用例之一赋值给 self
后,初始化过程才算完成。
与结构体类似,但与类不同,枚举是值类型;枚举实例在赋值给变量或常量,或作为参数传递给函数调用时会被复制。有关值类型的信息,请参阅结构体和枚举都是值类型。
您可以使用扩展声明来扩展枚举类型的行为,如扩展声明中所述。
任意类型用例的枚举
以下形式声明了一个包含任意类型枚举用例的枚举类型:
enum <#enumeration name#>: <#adopted protocols#> {
case <#enumeration case 1#>
case <#enumeration case 2#>(<#associated value types#>)
}
以这种形式声明的枚举在其他编程语言中有时被称为可区分联合。
在此形式中,每个 case
块由 case
关键字后跟一个或多个枚举用例组成,用例之间以逗号分隔。每个用例的名称必须是唯一的。每个用例还可以指定其存储给定类型的值。这些类型在关联值类型元组中指定,紧跟用例名称之后。
存储关联值的枚举成员可以用作函数,创建具有指定关联值的枚举实例。就像函数一样,你可以获取枚举成员的引用,并在之后的代码中使用它。
enum Number {
case integer(Int)
case real(Double)
}
let f = Number.integer
// f is a function of type (Int) -> Number
// Apply f to create an array of Number instances with integer values
let evenInts: [Number] = [0, 2, 4, 6].map(f)
有关更多信息以及具有关联值类型的案例示例,请参阅关联值。
间接枚举
枚举可以具有递归结构,也就是说,它们的分支可以具有与枚举类型本身的实例关联的值。但是,枚举类型的实例具有值语义,这意味着它们在内存中具有固定的布局。为了支持递归,编译器必须插入一个间接层。
要为特定枚举用例启用间接引用,请使用 indirect
声明修饰符标记它。间接用例必须具有关联值。
enum Tree<T> {
case empty
indirect case node(value: T, left: Tree, right: Tree)
}
为了对具有关联值的枚举的所有情况启用间接性,请使用 indirect
修饰符标记整个枚举 - 当枚举包含许多需要用 indirect
修饰符标记的情况时,这很方便。
带有 indirect
修饰符的枚举可以包含具有关联值的用例和不具有关联值的用例。但是,它不能包含任何也带有 indirect
修饰符的用例。
具有原始值类型用例的枚举
下面的形式声明了一个枚举类型,其中包含相同基本类型的枚举用例:
enum <#enumeration name#>: <#raw-value type#>, <#adopted protocols#> {
case <#enumeration case 1#> = <#raw value 1#>
case <#enumeration case 2#> = <#raw value 2#>
}
在这种形式中,每个 case
块由 case
关键字组成,后跟一个或多个枚举用例,用例之间用逗号分隔。与第一种形式中的情况不同,每个用例都有一个基础值,称为原始值,其基本类型相同。这些值的类型在原始值类型中指定,必须表示整数、浮点数、字符串或单个字符。具体来说,原始值类型必须符合 Equatable
协议和以下协议之一:对于整数字面量,ExpressibleByIntegerLiteral
协议;对于浮点数字面量,ExpressibleByFloatLiteral
;对于包含任意数量字符的字符串字面量,ExpressibleByStringLiteral
;对于仅包含单个字符的字符串字面量,ExpressibleByUnicodeScalarLiteral
或 ExpressibleByExtendedGraphemeClusterLiteral
协议。每个用例必须具有唯一的名称,并分配唯一的原始值。
如果原始值类型指定为 Int
,且未显式为各个案例赋值,则系统会为这些案例隐式赋值 0
、1
、2
,依此类推。每个未赋值的 Int
类型案例都会隐式赋值一个原始值,该值会根据前一个案例的原始值自动递增。
enum ExampleEnum: Int {
case a, b, c = 5, d
}
在上面的例子中,ExampleEnum.a
的原始值为 0
,ExampleEnum.b
的值为 1
。并且由于 ExampleEnum.c
的值被明确设置为 5
,因此 ExampleEnum.d
的值会自动从 5
递增,因此为 6
。
如果原始值类型指定为 String
,并且您没有明确为案例分配值,则每个未分配的案例都会隐式分配一个与该案例名称具有相同文本的字符串。
enum GamePlayMode: String {
case cooperative, individual, competitive
}
在上面的例子中,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
块中的枚举用例模式进行模式匹配,如枚举用例模式中所述。
枚举声明语法
枚举声明 → 属性? 访问级别修饰符? 联合样式枚举
枚举声明 → 属性? 访问级别修饰符? 原始值样式枚举
联合样式枚举 → indirect? enum 名称泛型参数子句? 类型继承子句? 泛型 where 子句? { 联合样式枚举成员? }
联合样式枚举成员 → 联合样式枚举成员联合样式枚举成员?
联合样式枚举成员 → 声明 | 联合样式枚举 case 子句 | 编译器控制语句
联合样式枚举案例子句 → 属性? indirect? case 联合样式枚举案例列表
联合样式枚举案例列表 → 联合样式枚举案例 | 联合样式枚举案例, 联合样式枚举案例列表
联合样式枚举大小写 → 枚举大小写名称元组类型?
枚举名称 → 标识符
枚举案例名称 → 标识符
原始值样式枚举 → enum 名称泛型参数子句? 类型继承子句泛型 where 子句? { 原始值样式枚举成员 }
原始值样式枚举成员 → 原始值样式枚举成员原始值样式枚举成员?
原始值样式枚举成员 → 声明 | 原始值样式枚举 case 子句 | 编译器控制语句
原始值样式枚举案例子句 → 属性? case 原始值样式枚举案例列表
原始值样式枚举案例列表 → 原始值样式枚举案例 | 原始值样式枚举案例, 原始值样式枚举案例列表
原始值样式枚举案例 → 枚举案例名称原始值赋值?
原始值赋值 → = 原始值字面量
原始值文字 → 数字文字 | 静态字符串文字 | 布尔文字
结构声明
结构体声明用于在程序中引入一个命名的结构体类型。结构体声明使用 struct
关键字,其形式如下:
struct <#structure name#>: <#adopted protocols#> {
<#declarations#>
}
结构体的主体包含零个或多个声明。这些声明可以包括存储属性和计算属性、类型属性、实例方法、类型方法、构造器、下标、类型别名,甚至其他结构体、类、参与者和枚举的声明。结构体声明不能包含析构器或协议声明。有关包含各种声明的结构体的讨论和示例,请参阅结构体和类。
结构类型可以采用任意数量的协议,但不能从类、枚举或其他结构继承。
有三种方法可以创建先前声明的结构的实例:
- 调用结构体中声明的其中一个初始化器,如初始化器中所述。
- 如果没有声明初始化器,调用结构体的成员初始化器,如结构体类型的成员初始化器中所述。
- 如果没有声明初始化器,并且所有结构体声明的属性都被赋予了初始值,调用结构体的默认初始化器,如默认初始化器中所述。
初始化结构体声明的属性的过程在初始化中描述。
结构体实例的属性可以使用点(.
)语法访问,如访问属性中所述。
结构体是值类型;结构体的实例在赋值给变量或常量,或作为参数传递给函数调用时会被复制。有关值类型的信息,请参阅结构体和枚举都是值类型。
您可以使用扩展声明来扩展结构体类型的行为,如扩展声明中所述。
结构声明语法
结构声明 → 属性? 访问级别修饰符? struct 结构体名称泛型参数子句? 类型继承子句? 泛型 where 子句? 结构体主体
结构体名称 → 标识符
结构体主体 → { 结构成员? }
结构成员 → 结构成员结构成员?
结构成员 → 声明 | 编译器控制语句
类声明
类声明用于在程序中引入一个命名的类类型。类声明使用 class
关键字声明,其形式如下:
class <#class name#>: <#superclass#>, <#adopted protocols#> {
<#declarations#>
}
类的主体包含零个或多个声明。这些声明可以包括存储属性和计算属性、实例方法、类型方法、构造器、单个析构器、下标、类型别名,甚至其他类、结构体、参与者和枚举的声明。类声明不能包含协议声明。有关包含各种声明的类的讨论和示例,请参阅结构体和类。
类类型只能从一个父类(即其超类)继承,但可以采用任意数量的协议。超类首先出现在类名和冒号之后,后跟所有采用的协议。泛型类可以继承自其他泛型类和非泛型类,但非泛型类只能继承自其他非泛型类。在冒号后写泛型超类的名称时,必须包含该泛型类的全名,包括其泛型参数子句。
正如在初始化器声明中讨论的那样,类可以拥有指定初始化器和便捷初始化器。类的指定初始化器必须初始化该类所有声明的属性,并且必须在调用其超类的任何指定初始化器之前完成初始化。
类可以重写其超类的属性、方法、下标和指定构造器。被重写的属性、方法、下标和指定构造器必须使用 override
声明修饰符进行标记。
要要求子类实现超类的构造器,请使用 required
声明修饰符标记超类的构造器。子类对该构造器的实现也必须使用 required
声明修饰符标记。
虽然超类中声明的属性和方法会被当前类继承,但超类中声明的指定初始化器只有当子类满足自动初始化器继承中描述的条件时才会被继承。Swift 类不会从通用基类继承。
有两种方法可以创建先前声明的类的实例:
使用点(.
)语法访问类实例的属性,如访问属性中所述。
类是引用类型;当类的实例被赋值给变量或常量,或者作为参数传递给函数调用时,类的实例会被引用,而不是被复制。有关引用类型的信息,请参阅类是引用类型。
您可以使用扩展声明来扩展类类型的行为,如扩展声明中所述。
类声明的语法
类声明 → 属性? 访问级别修饰符? final? class 类名泛型参数子句? 类型继承子句? 泛型 where 子句? 类体
类声明 → 属性? final 访问级别修饰符? class 类名泛型参数子句? 类型继承子句? 泛型 where 子句? 类体
类名 → 标识符
类主体 → { 类成员? }
类成员 → 类成员类成员?
类成员 → 声明 | 编译器控制语句
参与者声明
Actor 声明用于将一个命名的 Actor 类型引入到你的程序中。Actor 声明使用 actor
关键字进行声明,其形式如下:
actor <#actor name#>: <#adopted protocols#> {
<#declarations#>
}
参与者的主体包含零个或多个声明。这些声明可以包括存储属性和计算属性、实例方法、类型方法、构造器、单个析构器、下标、类型别名,甚至其他类、结构体和枚举的声明。有关包含各种声明的参与者的讨论和示例,请参阅参与者。
Actor 类型可以采用任意数量的协议,但不能从类、枚举、结构体或其他 Actor 继承。不过,带有 @objc
属性标记的 Actor 隐式遵循 NSObjectProtocol
协议,并作为 NSObject
的子类型暴露给 Objective-C 运行时。
有两种方法可以创建先前声明的参与者的实例:
默认情况下,Actor 的成员与该 Actor 隔离。代码(例如方法主体或属性的 getter)在该 Actor 上执行。Actor 内部的代码可以与这些成员同步交互,因为这些代码已在同一个 Actor 上运行;但 Actor 外部的代码必须使用 await
标记,以指示这些代码正在另一个 Actor 上异步运行。键路径不能引用 Actor 的隔离成员。Actor 隔离的存储属性可以作为输入输出参数传递给同步函数,但不能传递给异步函数。
Actor 还可以拥有非隔离成员,其声明以 nonisolated
关键字标记。非隔离成员的执行方式与 Actor 外部的代码相同:它无法与 Actor 的任何隔离状态交互,调用者在使用它时也无需将其标记为 await
。
只有当参与者的成员是非隔离的或异步的时,才可以使用 @objc
属性进行标记。
初始化参与者声明的属性的过程在初始化中描述。
可以使用点(.
)语法访问参与者实例的属性,如访问属性中所述。
Actor 是引用类型;当 Actor 实例被赋值给变量或常量,或者作为参数传递给函数调用时,Actor 的实例会被引用,而不是被复制。有关引用类型的信息,请参阅类是引用类型。
您可以使用扩展声明来扩展参与者类型的行为,如扩展声明中所述。
参与者声明的语法
参与者声明 → 属性? 访问级别修饰符? actor 参与者名称泛型参数子句? 类型继承子句? 泛型 where 子句? 参与者主体
演员姓名 → 标识符
演员主体 → { 演员成员? }
演员成员 → 演员成员演员成员?
参与者成员 → 声明 | 编译器控制语句
协议声明
协议声明用于在程序中引入一个命名的协议类型。协议声明使用 protocol
关键字,形式如下:
protocol <#protocol name#>: <#inherited protocols#> {
<#protocol member declarations#>
}
协议声明可以出现在全局范围内,或者嵌套在非泛型类型或非泛型函数内。
协议主体包含零个或多个协议成员声明,用于描述任何采用该协议的类型必须满足的一致性要求。具体来说,协议可以声明符合该协议的类型必须实现某些属性、方法、构造器和下标。协议还可以声明一种特殊的类型别名,称为关联类型,用于指定协议中各个声明之间的关系。协议声明不能包含类、结构体、枚举或其他协议声明。协议成员声明将在下文详细讨论。
协议类型可以从任意数量的其他协议继承。当一个协议类型从其他协议继承时,这些其他协议的要求会被汇总,并且任何从当前协议继承的类型都必须符合所有这些要求。有关如何使用协议继承的示例,请参阅协议继承。
您可以通过在该类型的扩展声明中采用该协议来为先前声明的类型添加协议一致性。在扩展中,您必须实现所采用协议的所有要求。如果该类型已经实现了所有要求,则可以将扩展声明的主体留空。
默认情况下,遵循协议的类型必须实现协议中声明的所有属性、方法和下标。也就是说,你可以使用 optional
声明修饰符标记这些协议成员声明,以指定遵循协议的类型对其的实现是可选的。optional
修饰符只能应用于标有 objc
特性的成员,并且只能应用于标有 objc
特性的协议成员。因此,只有类类型才能采用并遵循包含可选成员要求的协议。有关如何使用 optional
声明修饰符的更多信息,以及如何访问可选协议成员的指导(例如,当你不确定遵循协议的类型是否实现它们时),请参阅可选协议要求。
枚举的用例可以满足类型成员的协议要求。具体来说,没有任何关联值的枚举用例满足 Self
类型只读类型变量的协议要求;具有关联值的枚举用例满足返回 Self
的函数的协议要求,该函数的参数及其实参标签与用例的关联值匹配。例如:
protocol SomeProtocol {
static var someValue: Self { get }
static func someFunction(x: Int) -> Self
}
enum MyEnum: SomeProtocol {
case someValue
case someFunction(x: Int)
}
要将协议的采用限制为仅限类类型,请在冒号后的继承协议列表中包含 AnyObject
协议。例如,以下协议只能由类类型采用:
protocol SomeProtocol: AnyObject {
/* Protocol members go here */
}
任何从标有 AnyObject
要求的协议继承的协议同样只能被类类型采用。
笔记
如果协议标有objc
属性,AnyObject
要求都会隐式应用于该协议;无需明确使用AnyObject
要求标记该协议。
协议是命名类型,因此它们可以出现在代码中与其他命名类型相同的位置,如协议作为类型中所述。但是,您无法构造协议的实例,因为协议实际上并不提供其指定需求的实现。
您可以使用协议来声明类或结构的委托应该实现哪些方法,如委托中所述。
协议声明的语法
协议声明 → 属性? 访问级别修饰符? protocol 协议名称类型继承子句? 泛型 where 子句? 协议主体
协议名称 → 标识符
协议主体 → { 协议成员? }
协议成员 → 协议成员协议成员?
协议成员 → 协议成员声明 | 编译器控制语句
协议成员声明 → 协议属性声明
协议成员声明 → 协议方法声明
协议成员声明 → 协议初始化器声明
协议成员声明 → 协议下标声明
协议成员声明 → 协议关联类型声明
协议成员声明 → 类型别名声明
协议属性声明
协议通过在协议声明主体中包含协议属性声明来声明符合协议的类型必须实现某个属性。协议属性声明具有一种特殊的变量声明形式:
var <#property name#>: <#type#> { get set }
与其他协议成员声明一样,这些属性声明仅声明符合协议的类型所需的 getter 和 setter 方法。因此,您无需在声明该属性的协议中直接实现 getter 或 setter 方法。
遵循协议的类型可以通过多种方式满足 getter 和 setter 的要求。如果属性声明同时包含 get
和 set
关键字,则遵循协议的类型可以使用存储变量属性或可读可写的计算属性(即同时实现 getter 和 setter 的属性)来实现它。但是,该属性声明不能实现为常量属性或只读计算属性。如果属性声明仅包含 get
关键字,则可以将其实现为任何类型的属性。有关实现协议属性要求的遵循协议类型的示例,请参阅属性要求。
要在协议声明中声明类型属性要求,请使用 static
关键字标记属性声明。符合协议的结构体和枚举使用 static
关键字声明属性,符合协议的类使用 static
或 class
关键字声明属性。为结构体、枚举或类添加协议一致性的扩展使用与其扩展类型相同的关键字。为类型属性要求提供默认实现的扩展使用 static
关键字。
另请参阅变量声明。
协议属性声明的语法
协议属性声明 → 变量声明头变量名类型注解 getter-setter 关键字块
协议方法声明
协议通过在协议声明主体中包含协议方法声明来声明遵循协议的类型必须实现某个方法。协议方法声明的形式与函数声明相同,但有两个例外:它们不包含函数主体,并且不能在函数声明中提供任何默认参数值。有关实现协议方法要求的遵循类型的示例,请参阅方法要求。
要在协议声明中声明类或静态方法的要求,请使用 static
声明修饰符标记方法声明。符合协议的结构体和枚举使用 static
关键字声明方法,符合协议的类使用 static
或 class
关键字声明方法。为结构体、枚举或类添加协议一致性的扩展使用与其扩展类型相同的关键字。为类型方法要求提供默认实现的扩展使用 static
关键字。
另请参阅函数声明。
协议方法声明的语法
协议方法声明 → 函数头函数名泛型参数子句? 函数签名泛型 where 子句?
协议初始化器声明
协议声明遵循该协议的类型必须实现一个构造器,方法是在协议声明的主体中包含一个协议初始化器声明。协议初始化器声明的形式与构造器声明相同,只是前者不包含构造器的主体。
遵循协议的类型可以通过实现非失败初始化器或 init!
可失败初始化器来满足非失败协议初始化器的要求。遵循协议的类型可以通过实现任何类型的初始化器来满足可失败协议初始化器的要求。
当类实现初始化器以满足协议的初始化器要求时,如果该类尚未用 final
声明修饰符标记,则必须用 required
声明修饰符标记该初始化器。
另请参阅初始化声明。
协议初始化器声明的语法
协议初始化声明 → 初始化头泛型参数子句? 参数子句 throws 子句? 泛型 where 子句?
协议初始化声明 → 初始化头泛型参数子句? 参数子句 rethrows 泛型 where 子句?
协议下标声明
协议声明遵循该协议的类型必须通过在协议声明主体中包含协议下标声明来实现下标。协议下标声明具有一种特殊形式的下标声明:
subscript (<#parameters#>) -> <#return type#> { get set }
下标声明仅声明符合协议的类型的最低 getter 和 setter 实现要求。如果下标声明同时包含 get
和 set
关键字,则符合协议的类型必须同时实现 getter 和 setter 子句。如果下标声明仅包含 get
关键字,则符合协议的类型必须至少实现一个 getter 子句,并且可以选择实现一个 setter 子句。
要在协议声明中声明静态下标要求,请使用 static
声明修饰符标记下标声明。符合协议的结构体和枚举使用 static
关键字声明下标,符合协议的类使用 static
或 class
关键字声明下标。为结构体、枚举或类添加协议一致性的扩展使用与其扩展类型相同的关键字。为静态下标要求提供默认实现的扩展使用 static
关键字。
另请参阅下标声明。
协议下标声明的语法
协议下标声明 → 下标头下标结果泛型 where 子句? getter-setter 关键字块
协议关联类型声明
协议使用 associatedtype
关键字声明关联类型。关联类型为用作协议声明一部分的类型提供别名。关联类型类似于泛型参数子句中的类型参数,但它们与声明它们的协议中的 Self
关联。在此上下文中,Self
指的是最终符合该协议的类型。有关更多信息和示例,请参阅关联类型。
你可以在协议声明中使用泛型 where
子句,为从其他协议继承的关联类型添加约束,而无需重新声明关联类型。例如,下面的 SubProtocol
声明是等效的:
protocol SomeProtocol {
associatedtype SomeType
}
protocol SubProtocolA: SomeProtocol {
// This syntax produces a warning.
associatedtype SomeType: Equatable
}
// This syntax is preferred.
protocol SubProtocolB: SomeProtocol where SomeType: Equatable { }
另请参阅类型别名声明。
协议关联类型声明的语法
协议关联类型声明 → 属性? 访问级别修饰符? associatedtype 类型类型别名名称类型继承子句? 类型别名赋值? 泛型 where 子句?
初始化器声明
初始化器声明用于在程序中引入类、结构体或枚举的初始化器。初始化器声明使用 init
关键字进行声明,并有两种基本形式。
结构体、枚举和类类型可以拥有任意数量的构造器,但类构造器的规则和相关行为有所不同。与结构体和枚举不同,类有两种构造器:指定构造器和便捷构造器,详见初始化部分。
基本形式
以下形式声明结构体、枚举的初始化器以及类的指定初始化器:
init(<#parameters#>) {
<#statements#>
}
类的指定初始化器会直接初始化该类的所有属性。它不能调用同一类的任何其他初始化器;如果该类有超类,则必须调用超类的指定初始化器之一。如果该类从其超类继承了任何属性,则必须先调用超类的指定初始化器之一,然后才能在当前类中设置或修改这些属性。
指定初始化器只能在类声明的上下文中声明,因此不能使用扩展声明添加到类中。
结构体和枚举中的初始化器可以调用其他声明的初始化器来委托部分或全部初始化过程。
便捷初始化器
要为类声明便捷初始化器,请使用 convenience
声明修饰符标记初始化器声明。
convenience init(<#parameters#>) {
<#statements#>
}
便捷初始化器可以将初始化过程委托给另一个便捷初始化器或类的指定初始化器之一。也就是说,初始化过程必须以调用最终初始化类属性的指定初始化器结束。便捷初始化器不能调用超类的初始化器。
Required 初始化器
你可以使用 required
声明修饰符标记指定构造器和便捷构造器,以要求每个子类都实现该构造器。子类对该构造器的实现也必须使用 required
声明修饰符标记。
默认情况下,父类中声明的构造器不会被子类继承。也就是说,如果子类使用默认值初始化其所有存储属性,并且没有定义任何自己的构造器,则它将继承父类的所有构造器。如果子类重写了父类的所有指定构造器,则它将继承父类的便捷构造器。
与方法、属性和下标一样,您需要使用 override
声明修饰符标记被重写的指定初始化器。
笔记
如果使用required
声明修饰符标记初始化程序,则在子类中重写必需初始化程序时,不要使用override
修饰符标记初始化程序。
可抛出和异步初始化器
与函数和方法一样,初始化器可以抛出或重新抛出错误。并且,与函数和方法一样,你可以在初始化器参数后使用 throws
或 rethrows
关键字来指示相应的行为。同样,初始化器也可以是异步的,你可以使用 async
关键字来指示这一点。
要查看各种类型声明中的初始化器的示例,请参阅初始化。
可失败初始化器
可失败初始化器是一种构造器类型,它会生成一个可选实例,或者一个隐式解包的可选实例,该实例的类型与构造器声明的类型相同。因此,可失败初始化器可以返回 nil
来指示初始化失败。
要声明一个可失败的构造器来生成可选实例,请在构造器声明中的 init
关键字后添加一个问号 (init?
)。要声明一个可失败的构造器来生成隐式解包的可选实例,请添加一个感叹号 (init!
)。以下示例展示了一个 init?
可失败的构造器,它生成一个结构体的可选实例。
struct SomeStruct {
let property: String
// produces an optional instance of 'SomeStruct'
init?(input: String) {
if input.isEmpty {
// discard 'self' and return 'nil'
return nil
}
property = input
}
}
调用 init?
可失败初始化器的方式与调用非可失败初始化器的方式相同,只是必须处理结果的可选性。
if let actualInstance = SomeStruct(input: "Hello") {
// do something with the instance of 'SomeStruct'
} else {
// initialization of 'SomeStruct' failed and the initializer returned 'nil'
}
可失败的初始化器可以在初始化器主体的实现中的任何点返回 nil
。
可失败初始化器可以委托给任何类型的初始化器。非可失败初始化器可以委托给另一个非可失败初始化器或 init!
可失败初始化器。非可失败初始化器可以通过强制解包超类初始化器的结果来委托给 init?
可失败初始化器——例如,写成 super.init()!
。
初始化失败会通过初始化器委托传递。具体来说,如果一个可失败初始化器委托给一个失败并返回 nil
初始化器,那么委托的初始化器也会失败并隐式返回 nil
。如果一个非可失败初始化器委托给一个 init!
可失败初始化器,而该初始化器失败并返回 nil
,则会引发运行时错误(就像你使用 !
运算符解包了一个值为 nil
可选值一样)。
可失败指定构造器可以在子类中被任何类型的指定构造器重写。非可失败指定构造器只能在子类中被非可失败指定构造器重写。
欲了解更多信息并查看可失败初始化器的示例,请参阅可失败初始化器。
初始化器声明的语法
初始化器声明 → 初始化器头泛型参数子句? 参数子句 async? throws 子句? 泛型 where 子句? 初始化器主体
初始化器声明 → 初始化器头泛型参数子句? 参数子句 async? rethrows 泛型 where 子句? 初始化器主体
初始化头 → 属性? 声明修饰符? init
初始化头 → 属性? 声明修饰符? init?
初始化头 → 属性? 声明修饰符? init!
初始化程序主体 → 代码块
析构函数声明
析构器声明用于声明一个类类型的析构器。析构器不接受任何参数,其形式如下:
deinit {
<#statements#>
}
当类对象不再有任何引用时,在类对象被释放之前,析构器会被自动调用。析构器只能在类声明体中声明,而不能在类的扩展中声明,并且每个类最多只能有一个。
子类继承了超类的析构函数,该析构函数在子类对象被释放之前被隐式调用。子类对象只有在其继承链中的所有析构函数都执行完毕后才会被释放。
不直接调用反初始化函数。
有关如何在类声明中使用反初始化程序的示例,请参阅反初始化。
析构函数声明语法
析构器声明 → 属性? deinit 代码块
扩展声明
扩展声明允许您扩展现有类型的行为。扩展声明使用 extension
关键字声明,其形式如下:
extension <#type name#> where <#requirements#> {
<#declarations#>
}
扩展声明的主体包含零个或多个声明。这些声明可以包括计算属性、计算类型属性、实例方法、类型方法、构造器、下标声明,甚至类、结构体和枚举声明。扩展声明不能包含析构器或协议声明、存储属性、属性观察器或其他扩展声明。协议扩展中的声明不能标记为 final
。有关包含各种声明的扩展的讨论和示例,请参阅扩展。
如果类型名称是类、结构体或枚举类型,则扩展会扩展该类型。如果类型名称是协议类型,则扩展会扩展所有遵循该协议的类型。
扩展泛型类型或关联类型协议的扩展声明可以包含要求。如果扩展类型或遵循扩展协议的类型的实例满足要求,则该实例将获得声明中指定的行为。
扩展声明可以包含构造器声明。也就是说,如果你要扩展的类型是在另一个模块中定义的,则构造器声明必须委托给该模块中已定义的构造器,以确保该类型的成员得到正确初始化。
现有类型的属性、方法和初始化器不能在该类型的扩展中被覆盖。
协议一致性
扩展声明可以通过指定采用的协议为现有的类、结构或枚举类型添加协议一致性:
extension <#type name#>: <#adopted protocols#> where <#requirements#> {
<#declarations#>
}
扩展声明不能将类继承添加到现有类,因此只能在类型名称和冒号后指定协议列表。
条件一致性
您可以扩展泛型类型,使其有条件地遵循某个协议,这样,该类型的实例只有在满足特定要求时才遵循该协议。您可以通过在扩展声明中添加要求来为协议添加条件遵循性。
在某些通用上下文中不使用覆盖的要求
在某些泛型上下文中,通过条件符合协议而获得行为的类型并不总是使用该协议要求的专门实现。为了说明这种行为,以下示例定义了两个协议和一个条件符合这两个协议的泛型类型。
protocol Loggable {
func log()
}
extension Loggable {
func log() {
print(self)
}
}
protocol TitledLoggable: Loggable {
static var logTitle: String { get }
}
extension TitledLoggable {
func log() {
print("\(Self.logTitle): \(self)")
}
}
struct Pair<T>: CustomStringConvertible {
let first: T
let second: T
var description: String {
return "(\(first), \(second))"
}
}
extension Pair: Loggable where T: Loggable { }
extension Pair: TitledLoggable where T: TitledLoggable {
static var logTitle: String {
return "Pair of '\(T.logTitle)'"
}
}
extension String: TitledLoggable {
static var logTitle: String {
return "String"
}
}
每当 Pair
结构的泛型类型符合 Loggable
或 TitledLoggable
时,该结构体也分别符合 Loggable
和 TitledLoggable
。在下面的示例中,oneAndTwo
是 Pair<String>
的一个实例,由于 String
符合 TitledLoggable
协议,因此它符合 TitledLoggable
协议。当直接在 oneAndTwo
上调用 log()
方法时,将使用包含标题字符串的专用版本。
let oneAndTwo = Pair(first: "one", second: "two")
oneAndTwo.log()
// Prints "Pair of 'String': (one, two)"
但是,当 oneAndTwo
在通用上下文中使用或作为 Loggable
协议的实例时,不会使用专用版本。Swift 会根据 Pair
需要遵循 Loggable
的最低要求来选择调用哪个 log()
实现。因此,会使用 Loggable
协议提供的默认实现。
func doSomething<T: Loggable>(with x: T) {
x.log()
}
doSomething(with: oneAndTwo)
// Prints "(one, two)"
当在传递给 doSomething(_:)
的实例上调用 log()
时,自定义标题将从记录的字符串中省略。
协议一致性不能冗余
具体类型只能遵循特定协议一次。Swift 将重复遵循协议标记为错误。您可能在两种情况下遇到此类错误。第一种情况是,您显式地遵循同一协议多次,但要求不同。第二种情况是,您隐式地从同一协议继承多次。这些情况将在以下部分讨论。
解决显式冗余
同一个具体类型的多个扩展不能遵循同一个协议,即使这些扩展的要求是互斥的。以下示例演示了此限制。两个扩展声明尝试遵循 Serializable
协议的条件,一个用于包含 Int
元素的数组,另一个用于包含 String
元素的数组。
protocol Serializable {
func serialize() -> Any
}
extension Array: Serializable where Element == Int {
func serialize() -> Any {
// implementation
}
}
extension Array: Serializable where Element == String {
func serialize() -> Any {
// implementation
}
}
// Error: redundant conformance of 'Array<Element>' to protocol 'Serializable'
如果需要根据多种具体类型添加条件一致性,请创建每种类型都可以遵循的新协议,并在声明条件一致性时使用该协议作为要求。
protocol SerializableInArray { }
extension Int: SerializableInArray { }
extension String: SerializableInArray { }
extension Array: Serializable where Element: SerializableInArray {
func serialize() -> Any {
// implementation
}
}
解决隐式冗余
当具体类型有条件地符合协议时,该类型隐式地符合任何具有相同要求的父协议。
如果您需要某个类型有条件地遵循从单个父级继承的两个协议,请显式声明遵循父级协议。这样可以避免隐式地遵循两次具有不同要求的父级协议。
下面的示例明确声明了 Array
与 Loggable
的条件一致性,以避免在声明其与 TitledLoggable
和新的 MarkedLoggable
协议的条件一致性时发生冲突。
protocol MarkedLoggable: Loggable {
func markAndLog()
}
extension MarkedLoggable {
func markAndLog() {
print("----------")
log()
}
}
extension Array: Loggable where Element: Loggable { }
extension Array: TitledLoggable where Element: TitledLoggable {
static var logTitle: String {
return "Array of '\(Element.logTitle)'"
}
}
extension Array: MarkedLoggable where Element: MarkedLoggable { }
如果没有扩展明确声明对 Loggable
的条件一致性,其他 Array
扩展将隐式创建这些声明,从而导致错误:
extension Array: Loggable where Element: TitledLoggable { }
extension Array: Loggable where Element: MarkedLoggable { }
// Error: redundant conformance of 'Array<Element>' to protocol 'Loggable'
扩展声明的语法
扩展声明 → 属性? 访问级别修饰符? extension 类型标识符类型继承子句? 泛型 where 子句? 扩展主体
扩展主体 → { 扩展成员? }
扩展成员 → 扩展成员扩展成员?
扩展成员 → 声明 | 编译器控制语句
下标声明
下标声明允许你为特定类型的对象添加下标支持,通常用于提供访问集合、列表或序列中元素的便捷语法。下标声明使用 subscript
关键字声明,形式如下:
subscript (<#parameters#>) -> <#return type#> {
get {
<#statements#>
}
set(<#setter name#>) {
<#statements#>
}
}
下标声明只能出现在类、结构、枚举、扩展或协议声明的上下文中。
参数指定一个或多个用于访问下标表达式中相应类型元素的索引(例如,表达式 object[i]
中的 i
)。虽然用于访问元素的索引可以是任意类型,但每个参数必须包含类型注释以指定每个索引的类型。返回类型指定被访问元素的类型。
与计算属性一样,下标声明支持读取和写入所访问元素的值。getter 用于读取值,setter 用于写入值。setter 子句是可选的,当只需要 getter 时,可以省略这两个子句,直接返回请求的值。但是,如果提供了 setter 子句,则也必须提供 getter 子句。
设置器名称和括号是可选的。如果提供了设置器名称,它将被用作设置器参数的名称。如果没有提供设置器名称,则设置器的默认参数名称为 value
。设置器参数的类型与返回值 type
相同。
你可以使用声明下标的类型来重载它,只要参数或返回类型与重载的类型不同即可。你也可以覆盖从超类继承的下标声明。执行此操作时,必须使用 override
标声明。
下标参数遵循与函数参数相同的规则,但有两个例外。默认情况下,下标中使用的参数没有实参标签,这与函数、方法和初始化器不同。但是,您可以使用与函数、方法和初始化器相同的语法提供显式实参标签。此外,下标不能包含输入输出参数。下标参数可以具有默认值,使用特殊类型的参数中描述的语法。
您还可以在协议声明的上下文中声明下标,如协议下标声明中所述。
有关下标的更多信息以及下标声明的示例,请参阅下标。
类型下标声明
要声明由类型(而非类型实例)公开的下标,请使用 static
声明修饰符标记下标声明。类可以使用 class
声明修饰符标记类型计算属性,以允许子类覆盖超类的实现。在类声明中,static
关键字与同时使用 class
和 final
声明修饰符标记声明具有相同的效果。
下标声明语法
下标声明 → 下标头下标结果泛型 where 子句? 代码块
下标声明 → 下标头下标结果泛型 where 子句? getter-setter 块
下标声明 → 下标头下标结果泛型 where 子句? getter-setter 关键字块
下标头 → 属性? 声明修饰符? subscript 泛型参数子句? 参数子句
下标结果 → -> 属性? 类型
宏声明
宏声明引入一个新的宏。它以 macro
关键字开头,形式如下:
macro <#name#> = <#macro implementation#>
宏的实现是另一个宏,它指示执行此宏扩展的代码的位置。执行宏扩展的代码是一个单独的 Swift 程序,它使用 SwiftSyntax
模块与 Swift 代码交互。从 Swift 标准库中调用 external Macro(module: type:)
宏,并传入包含该宏实现的类型的名称以及包含该类型的模块的名称。
宏可以重载,其模型与函数相同。宏声明仅出现在文件范围内。
有关 Swift 中宏的概述,请参阅宏。
宏声明语法
宏声明 → 宏头标识符泛型参数子句? 宏签名宏定义? 泛型 where 子句
宏头 → 属性? 声明修饰符? macro
宏签名 → 参数子句宏函数签名结果?
宏函数签名结果 → -> 类型
宏定义 → = 表达式
运算符声明
运算符声明在程序中引入新的中缀、前缀或后缀运算符,并使用 operator
关键字进行声明。
你可以声明三种不同固定性的运算符:中缀、前缀和后缀。运算符的固定性指定了运算符相对于其操作数的相对位置。
运算符声明有三种基本形式,分别对应一种固定性。运算符的固定性通过在 operator
关键字前 infix
、prefix
或 postfix
声明修饰符来标记运算符声明来指定。在每种形式中,运算符的名称只能包含在运算符中定义的运算符字符。
中缀运算符
以下形式声明了一个新的中缀运算符:
infix operator <#operator name#>: <#precedence group#>
中缀运算符是写在两个操作数之间的二元运算符,例如表达式 1 + 2
中我们熟悉的加法运算符 (+
)。
中缀运算符可以选择性地指定优先级组。如果省略运算符的优先级组,Swift 将使用默认优先级组 DefaultPrecedence
,其优先级略高于 TernaryPrecedence
。更多信息,请参阅优先级组声明。
前缀运算符
下面的形式声明了一个新的前缀运算符:
prefix operator <#operator name#>
前缀运算符是紧挨着其操作数的一元运算符,例如表达式 !a
中的前缀逻辑非运算符 (!
)。
前缀运算符声明不指定优先级。前缀运算符是非结合性的。
后缀运算符
以下形式声明了一个新的后缀运算符:
postfix operator <#operator name#>
后缀运算符是紧跟其操作数之后的一元运算符,例如表达式 a!
中的后缀强制解包运算符 (!
)。
与前缀运算符一样,后缀运算符声明不指定优先级级别。后缀运算符是非结合性的。
声明新运算符后,可以通过声明与该运算符同名的静态方法来实现它。静态方法是该运算符所接受值类型的成员。例如,将 Double
乘以 Int
的运算符在 Double
或 Int
结构上实现为静态方法。如果要实现前缀或后缀运算符,则还必须使用相应的 prefix
或 postfix
声明修饰符标记该方法声明。要查看如何创建和实现新运算符的示例,请参阅自定义运算符。
运算符声明的语法
运算符声明 → 前缀运算符声明 | 后缀运算符声明 | 中缀运算符声明
前缀运算符声明 → prefix operator 运算符
后缀运算符声明 → postfix operator 运算符
中缀运算符声明 → infix operator 运算符中缀运算符组?
中缀运算符组 → : 优先级组名称
优先级组声明
优先级组声明为程序中的中缀运算符优先级引入了新的分组。运算符的优先级指定了在没有分组括号的情况下,运算符与其操作数的绑定程度。
优先组声明具有以下形式:
precedencegroup <#precedence group name#> {
higherThan: <#lower group names#>
lowerThan: <#higher group names#>
associativity: <#associativity#>
assignment: <#assignment#>
}
较低优先级组名称和较高优先级组名称列表指定了新优先级组与现有优先级组的关系。lowerThan
优先级组属性只能用于引用在当前模块之外声明的优先级组。当两个运算符相互竞争操作数时,例如在表达式 2 + 3 * 5
中,相对优先级较高的运算符与其操作数的绑定更紧密。
笔记
使用较低组名和较高组名相互关联的优先级组必须符合单个关系层次结构,但它们不必构成线性层次结构。这意味着优先级组可以具有未定义的相对优先级。这些优先级组中的运算符不能在没有括号的情况下彼此相邻使用。
Swift 定义了许多优先级组,以配合 Swift 标准库提供的运算符。例如,加法 (+
) 和减法 (-
) 运算符属于 AdditionPrecedence
组,乘法 (*
) 和除法 (/
) 运算符属于 MultiplicationPrecedence
组。有关 Swift 标准库提供的优先级组的完整列表,请参阅运算符声明。
运算符的结合性指定在没有分组括号的情况下如何将具有相同优先级的运算符序列分组在一起。您可以通过编写上下文相关关键字 left
、right
或 none
之一来指定运算符的结合性——如果省略结合性,则默认值为 none
。左结合的运算符从左到右分组。例如,减法运算符 (-
) 是左结合的,因此表达式 4 - 5 - 6
被分组为 (4 - 5) - 6
并计算为 -7
。右结合的运算符从右到左分组,而结合性为 none
的运算符根本不结合。相同优先级的非结合运算符不能彼此相邻出现。例如,<
运算符的结合性为 none
,这意味着 1 < 2 < 3
不是有效表达式。
优先级组的赋值指定了运算符在包含可选链式操作的运算中的优先级。设置为 true
时,相应优先级组中的运算符在可选链式操作期间使用与 Swift 标准库中的赋值运算符相同的分组规则。设置为 false
或省略时,优先级组中的运算符遵循与不执行赋值的运算符相同的可选链式操作规则。
优先组声明语法
优先级组声明 → precedencegroup 优先级组名称 { 优先级组属性? }
优先级组属性 → 优先级组属性优先级组属性?
优先级组属性 → 优先级组关系
优先级组属性 → 优先级组分配
优先级组属性 → 优先级组关联性
优先级组关系 → higherThan: 优先级组名称
优先级组关系 → lowerThan: 优先级组名称
优先组赋值 → assignment: 布尔文字
优先组结合性 → associativity: left
优先组结合性 → associativity: right
优先组关联性 → associativity: none
优先级组名称 → 优先级组名称 | 优先级组名称, 优先级组名称
优先级组名称 → 标识符
声明修饰符
声明修饰符是用于修改声明行为或含义的关键字或上下文相关关键字。您可以通过在声明的属性(如果有)和引入该声明的关键字之间写入适当的关键字或上下文相关关键字来指定声明修饰符。
类成员修饰符
class
将此修饰符应用于类的成员,表示该成员是类本身的成员,而不是类实例的成员。具有此修饰符且不具有 final
修饰符的超类成员可以被子类覆盖。
dynamic
将此修饰符应用于任何可以用 Objective-C 表示的类成员。当您使用 dynamic
修饰符标记成员声明时,对该成员的访问始终会使用 Objective-C 运行时动态调度。编译器永远不会内联或取消虚拟化对该成员的访问。
由于标有 dynamic
修饰符的声明是使用 Objective-C 运行时分派的,因此必须使用 objc
属性进行标记。
final
将此修饰符应用于类或类的属性、方法或下标成员。应用于类时,表示该类不能被子类化。应用于类的属性、方法或下标时,表示类成员不能在任何子类中被覆盖。有关如何使用 final
属性的示例,请参阅防止覆盖。
lazy
将此修饰符应用于类或结构的存储变量属性,以指示该属性的初始值最多在首次访问时计算并存储一次。有关如何使用 lazy
修饰符的示例,请参阅延迟存储属性。
optional
将此修饰符应用于协议的属性、方法或下标成员,以指示不需要符合类型来实现这些成员。
您只能将 optional
修饰符应用于标有 objc
特性的协议。因此,只有类类型才能采用并遵循包含可选成员要求的协议。有关如何使用 optional
修饰符的更多信息,以及如何访问可选协议成员(例如,当您不确定符合协议的类型是否实现了这些成员时),请参阅可选协议要求。
required
将此修饰符应用于类的指定或便捷构造器,以指示每个子类都必须实现该构造器。子类对该构造器的实现也必须使用 required
修饰符进行标记。
static
将此修饰符应用于结构体、类、枚举或协议的成员,以指示该成员是该类型的成员,而不是该类型实例的成员。在类声明的范围内,在成员声明上使用 static
修饰符与在该成员声明上使用 class
和 final
修饰符具有相同的效果。但是,类的常量类型属性是个例外:static
在此处具有其正常的非类含义,因为您不能在这些声明上写 class
或 final
。
引用修饰符
unowned
将此修饰符应用于存储变量、常量或存储属性,以指示该变量或属性具有对其值存储的对象的无主引用。如果在对象被释放后尝试访问该变量或属性,则会引发运行时错误。与弱引用类似,属性或值的类型必须是类类型;与弱引用不同,该类型是非可选的。有关 unowned
修饰符的示例和更多信息,请参阅无主引用。
unowned(safe)
unowned
的明确拼写。
unowned(unsafe)
将此修饰符应用于存储变量、常量或存储属性,以指示该变量或属性具有对存储为其值的对象的无主引用。如果您在对象被释放后尝试访问该变量或属性,您将访问该对象原来所在位置的内存,这是一个内存不安全的操作。与弱引用类似,属性或值的类型必须是类类型;与弱引用不同,该类型是非可选的。有关 unowned
修饰符的示例和更多信息,请参阅无主引用。
weak
将此修饰符应用于存储变量或存储变量属性,以指示该变量或属性对其值所存储的对象具有弱引用。变量或属性的类型必须是可选的类类型。如果在对象被释放后访问该变量或属性,则其值为 nil
。有关 weak
修饰符的示例和更多信息,请参阅弱引用。
访问控制级别
Swift 提供五种访问控制级别:开放 (open
)、公共 (public
)、内部 (internal
)、文件私有 (fileprivate
) 和私有 (private
)。您可以使用以下访问级别修饰符之一标记声明,以指定其访问级别。访问控制的详细讨论请参阅访问控制。
open
将此修饰符应用于声明,指示该声明可由与其位于同一模块中的代码访问和创建子类。标有 open
访问级别修饰符的声明也可由导入包含该声明的模块的代码访问和创建子类。
public
将此修饰符应用于声明,指示该声明可由与其位于同一模块中的代码访问和子类化。标有 public
访问级别修饰符的声明也可由导入包含该声明的模块的代码访问(但不能子类化)。
package
将此修饰符应用于声明,以指示该声明只能由与其位于同一软件包中的代码访问。软件包是您在使用的构建系统中定义的代码分发单元。构建系统编译代码时,会通过将 -package-name
标志传递给 Swift 编译器来指定软件包名称。如果构建系统在构建两个模块时指定了相同的软件包名称,则这两个模块属于同一个软件包。
internal
将此修饰符应用于声明,指示该声明只能由与其位于同一模块中的代码访问。默认情况下,大多数声明都隐式地使用 internal
访问级别修饰符进行标记。
fileprivate
将此修饰符应用于声明,以指示该声明只能由与声明位于同一源文件中的代码访问。
private
将此修饰符应用于声明,以指示该声明只能由声明的直接封闭范围内的代码访问。
为了实现访问控制,扩展的行为如下:
- 如果同一个文件中有多个扩展,并且这些扩展都扩展了相同的类型,则所有这些扩展都具有相同的访问控制范围。扩展及其扩展的类型可以位于不同的文件中。
- 如果扩展与它们扩展的类型位于同一文件中,则扩展具有与它们扩展的类型相同的访问控制范围。
- 类型声明中声明的私有成员可以被该类型的扩展访问。在一个扩展中声明的私有成员可以被其他扩展以及该扩展类型的声明访问。
上述每个访问级别修饰符都可以接受一个参数,该参数由括号括起来的 set
关键字组成,例如 private(set)
。当你希望为变量或下标的 set
方法指定一个小于或等于变量或下标本身访问级别的访问级别时,请使用这种形式的访问级别修饰符,正如Getter 和 Setter中所述。
声明修饰符的语法
声明修饰符 → class | convenience | dynamic | final | infix | lazy | optional | override | postfix | prefix | required | static | unowned | unowned(safe) | unowned(unsafe) | weak
声明修饰符 → 访问级别修饰符
声明修饰符 → 突变修饰符
声明修饰符 → 参与者隔离修饰符
声明修饰符 → 声明修饰符声明修饰符?
访问级别修饰符 → private | private(set)
访问级别修饰符 → fileprivate | fileprivate(set)
访问级别修饰符 → internal | internal(set)
访问级别修饰符 → package | package(set)
访问级别修饰符 → public | public(set)
访问级别修饰符 → open | open(set)
突变修饰符 → mutating | nonmutating
参与者隔离修饰符 → nonisolated