Swift51.com

声明(Declarations)


1.0 翻译:marsprince Lenhoon(微博) 校对:numbbbbb, stanzhai

2.0 翻译+校对:Lenhoon, BridgeQ

2.1 翻译:mmoaay, shanks 校对:shanks

本页包含内容:

声明可以在程序里引入新的名字或者构造。举例来说,可以使用声明来引入函数和方法,变量和常量,或者来定义新的命名的枚举,结构,类和协议类型。还可以使用声明来扩展一个已经存在的命名的类型的行为,或者在程序里引入在其它地方声明的符号。

在 Swift 中,大多数声明在某种意义上讲也是定义,实现或初始化往往伴随着声明。这意味着,由于协议往往并不提供实现,大多数协议仅仅只是声明而已。为了方便起见,也因为这些区别在 Swift 中不是很重要,“声明”这个术语同时包含了声明和定义。

声明语法
</a> 声明导入声明
声明常量声明
声明变量声明
声明类型别名声明
声明函数声明
声明枚举声明
声明结构体声明
声明类声明
声明协议声明
声明构造器声明
声明析构器声明
声明扩展声明
声明下标声明
声明运算符声明
多条声明声明 多条声明可选

顶级代码

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

顶级声明语法
顶级声明多条语句可选

代码块

代码块可以将一些声明和控制结构组织在一起。它有如下的形式:

{
    语句
}

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

代码块语法
代码块{ 多条语句可选 }

导入声明

导入声明让你可以使用在其他文件中声明的内容。导入语句的基本形式是导入整个模块,它由 import 关键字开始,后面紧跟一个模块名:

import 模块

可以对导入提供更细致的控制,如指定一个特殊的子模块或者指定一个模块或子模块中的某个声明。提供了这些限制后,在当前作用域中,只有导入的符号是可用的,而不是整个模块。

import 导入类型 模块.符号名
import 模块.子模块

导入声明语法
</a> 导入声明特性列表可选 import 导入类型可选 导入路径
导入类型typealias | struct | class | enum | protocol | var | func
</a> 导入路径导入路径标识符 | 导入路径标识符 . 导入路径
导入路径标识符标识符 | 运算符

常量声明

常量声明可以在程序中命名一个常量。常量以关键字 let 来声明,遵循如下的格式:

let 常量名称: 类型 = 表达式

当常量的值被给定后,常量就将常量名称和表达式的值绑定在了一起,不能更改。

这意味着,如果常量以类对象来初始化,对象本身的内容是可以改变的,但是常量和该对象之间的结合关系是不能改变的。

当一个常量被声明为全局变量,它必须被给定一个初始值。在类或者结构体中声明一个常量时,它被认为是一个常量属性。常量声明不能是计算型属性,因此也没有存取方法。

如果常量名称是元组形式,元组中的每一项的名称会和对应的初始化表达式的值绑定。

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

在上例中,firstNumber 是一个值为 10 的常量,secnodeName 是一个值为 42 的常量。所有常量都可以独立的使用:

print("The first number is \(firstNumber).")
// 打印 “The first number is 10.”
print("The second number is \(secondNumber).")
// 打印 “The second number is 42.”

当常量名称的类型可以被推断出时,类型标注在常量声明中是一个可选项,正如 类型推断 中所描述的。

声明一个常量类型属性要使用 static 声明修饰符。类型属性在 类型属性中有介绍。

如果还想获得更多关于常量的信息或者想在使用中获得帮助,请参阅 常量和变量存储属性

常量声明语法
</a> 常量声明特性列表可选 声明修饰符列表可选 let 模式构造器列表
模式构造器列表模式构造器 | 模式构造器 , 模式构造器列表
</a> 模式构造器模式 构造器可选
构造器= 表达式

变量声明

变量声明可以在程序中声明一个变量,它以关键字 var 来声明。

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

注意
也可以在协议声明的上下文中声明属性,详情请参阅 协议属性声明

可以在子类中使用 override 声明修饰符来标记继承来的属性的声明,从而重写属性,详情请参阅 重写

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

使用如下形式声明一个存储型变量或存储型变量属性:

var 变量名称: 类型 = 表达式

可以在全局范围,函数内部,或者在类和结构体的声明中使用这种形式来声明一个变量。当变量以这种形式在全局范围或者函数内部被声明时,它代表一个存储型变量。当它在类或者结构体中被声明时,它代表一个存储型变量属性。

用于初始化的表达式不可以在协议的声明中出现,在其他情况下,该表达式是可选的。如果没有初始化表达式,那么变量声明必须包含类型标注。

对于常量的声明,如果变量名称是一个元组,元组中每一项的名称都要和初始化表达式中的相应值绑定。

正如名字一样,存储型变量和存储型变量属性的值会存储在内存中。

计算型变量和计算型属性

使用如下形式声明一个计算型变量或计算型属性:

var 变量名称: 类型 {  
    get {  
        语句
    }  
    set(setter 名称) {  
        语句
    }  
}

可以在全局范围,函数内部,以及类,结构体,枚举,扩展声明的上下文中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,它代表一个计算型变量。当它在类,结构体,枚举,扩展声明的上下文中被声明时,它代表一个计算型属性。

getter 用来读取变量值,setter 用来写入变量值。setter 子句是可选的,getter 子句是必须的。也可以将这些子句都省略,直接返回请求的值,正如在 只读计算型属性 中描述的那样。但是如果提供了一个 setter 子句,也必须提供一个 getter 子句。

圆括号内的 setter 的名称是可选的。如果提供了一个 setter 名称,它就会作为 setter 的参数名称使用。如果不提供 setter 名称,setter 的参数的默认名称为 newValue,正如在 便捷 setter 声明 中描述的那样。

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

要获得更多关于计算型属性的信息和例子,请参阅 计算型属性

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

可以在声明存储型变量或属性时提供 willSetdidSet 观察器。一个包含观察器的存储型变量或属性以如下形式声明:

var 变量名称: 类型 = 表达式 {  
    willSet(setter 名称) {  
        语句 
    }  
    didSet(setter 名称) {  
        语句  
    }  
}

可以在全局范围,函数内部,或者类,结构体声明的上下文中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,观察器代表一个存储型变量观察器。当它在类,结构体声明的上下文中被声明时,观察器代表一个属性观察器。

可以为任何存储型属性添加观察器。也可以通过重写父类属性的方式为任何继承的属性(无论是存储型还是计算型的)添加观察器 ,正如 重写属性观察器 中所描述的。

用于初始化的表达式在一个类或者结构体的声明中是可选的,但是在其他地方是必须的。如果类型可以从初始化表达式中推断而来,那么这个类型标注是可选的。

当变量或属性的值被改变时,willSetdidSet 观察器提供了一种观察方法。观察器不会在变量或属性第一次初始化时被调用,它们仅当值在初始化环境之外被改变时才会被调用。

willSet 观察器只在变量或属性的值被改变之前调用。新的值作为一个常量传入 willSet 观察器,因此不可以在 willSet 中改变它。didSet 观察器在变量或属性的值被改变后立即调用。和 willSet 观察器相反,为了方便获取旧值,旧值会传入 didSet 观察器。这意味着,如果在变量或属性的 didiset 观察器中设置值,设置的新值会取代刚刚在 willSet 观察器中传入的那个值。

willSetdidSet 中,圆括号以及其中的 setter 名称是可选的。如果提供了一个 setter 名称,它就会作为 willSetdidSet 的参数被使用。如果不提供 setter 名称,willSet 观察器的默认参数名为 newValuedidSet 观察器的默认参数名为 oldValue

提供了 willSet 时,didSet 是可选的。同样的,提供了 didSet 时,willSet 则是可选的。

要获得更多信息以及查看如何使用属性观察器的例子,请参阅 属性观察器

类型变量属性

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

注意
在一个类声明中,使用关键字 static 与同时使用 classfinal 去标记一个声明的效果相同。

变量声明语法

变量声明变量声明头 模式构造器列表
变量声明变量声明头 变量名称 类型标注 代码块
变量声明变量声明头 变量名称 类型标注 getter-setter代码块
变量声明变量声明头 变量名称 类型标注 getter-setter关键字代码块
变量声明变量声明头 变量名称 构造器 willSet-didSet代码块
变量声明变量声明头 变量名称 类型标注 构造器可选 willSet-didSet代码块

变量声明头特性列表可选 声明修饰符列表可选 var
变量名称标识符

getter-setter 代码块代码块
getter-setter 代码块{ getter子句 setter子句可选 }
getter-setter 代码块{ setter子句 getter子句 }
</a> getter 子句特性列表可选 get 代码块
setter 子句特性列表可选 set setter名称可选 代码块
setter 名称( 标识符 )

getter-setter 关键字代码块{ getter关键字子句 setter关键字子句可选 }
getter-setter 关键字代码块{ setter关键字子句 getter关键字子句 }
</a> getter 关键字子句特性列表可选 get
setter 关键字子句特性列表可选 set

willSet-didSet 代码块{ willSet子句 didSet子句可选 }
willSet-didSet 代码块{ didSet子句 willSet子句可选 }
</a> willSet 子句特性列表可选 willSet setter名称可选 代码块
didSet 子句特性列表可选 didSet setter名称可选 代码块

类型别名声明

类型别名声明可以在程序中为一个现存类型声明一个别名。类型别名声明语句使用关键字 typealias 声明,遵循如下的形式:

类型别名 = 现存类型

当声明一个类型的别名后,可以在程序的任何地方使用别名来代替现存类型。现存类型可以是命名类型或者混合类型。类型别名不产生新的类型,它只是可以使用别名来引用现存类型。

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

类型别名声明语法
</a> 类型别名声明类型别名头 类型别名赋值
类型别名头特性列表可选 访问级别修饰符可选 typealias 类型别名名称
</a> 类型别名名称标识符
类型别名赋值= 类型

函数声明

使用函数声明在程序中引入新的函数或者方法。当一个函数被声明在类,结构体,枚举,或者协议的上下文中,会作为一个方法。函数声明使用关键字 func,遵循如下的形式:

func 函数名称(参数列表) -> 返回类型 {  
    语句  
}

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

func 函数名称(参数列表) {  
    语句  
}

每个参数的类型都要标明,它们不能被推断出来。虽然函数的参数默认是常量,也可以在参数名前添加 let 来强调这一行为。在参数名前面添加 var 则会使它们成为变量,作用域内任何对变量的改变只在函数体内有效,或者用 inout 使这些改变可以在调用域内生效。更多关于 inout 参数的讨论,请参阅 输入输出参数

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

函数定义可以出现在另一个函数声明内。这种函数被称作嵌套函数。更多关于嵌套函数的讨论,请参阅 嵌套函数

参数名

函数的参数列表由一个或多个函数参数组成,参数间以逗号分隔。函数调用时的参数顺序必须和函数声明时的参数顺序一致。最简单的参数列表有着如下的形式:

参数名称: 参数类型

一个参数有一个内部名称,这个内部名称可以在函数体内被使用。同样也可以作为外部名称,当调用函数时这个外部名称被作为实参的标签来使用。默认情况下,第一个参数的外部名称省略不写,第二个和之后的参数使用它们的内部名称作为它们的外部名称。例如:

func f(x: Int, y: Int) -> Int { return x + y }
f(1, y: 2) // 参数 y 有标签,参数 x 则没有

可以按照如下两种形式之一,重写参数名称的默认行为:

外部参数名称 内部参数名称: 参数类型
_ 内部参数名称: 参数类型

在内部参数名称前的名称赋予这个参数一个外部名称,这个名称可以和内部参数的名称不同。外部参数名称在函数被调用时必须被使用。对应的参数在方法或函数被调用时必须有外部名。

内部参数名称前的下划线(_)使该参数在函数被调用时没有名称。在函数或方法调用时,对应的参数必须没有名字。

func f(x x: Int, withY y: Int, _ z: Int) -> Int { return x + y + z }
f(x: 1, withY: 2, 3) // 参数 x 和 y 是有标签的,参数 z 则没有

输入输出参数

输入输出参数被传递时遵循如下规则:

  1. 函数调用时,参数的值被拷贝。
  2. 函数体内部,拷贝后的值被修改。
  3. 函数返回后,拷贝后的值被赋值给原参数。

这种行为被称为拷入拷出(copy-in copy-out)或值结果调用(call by value result)。例如,当一个计算型属性或者一个具有属性观察器的属性被用作函数的输入输出参数时,其 getter 会在函数调用时被调用,而其 setter 会在函数返回时被调用。

作为一种优化手段,当参数值存储在内存中的物理地址时,在函数体内部和外部均会使用同一内存位置。这种优化行为被称为引用调用(call by reference),它满足了拷入拷出模型的所有需求,而消除了复制带来的开销。不要依赖于拷入拷出与引用调用之间的行为差异。

不要使用传递给输入输出参数的值,即使原始值在当前作用域中依然可用。当函数返回时,你对原始值所做的更改会被拷贝的值所覆盖。不要依赖于引用调用的优化机制来试图阻止这种覆盖。

你不能将同一个值传递给多个输入输出参数,因为多个输入输出参数引发的拷贝与覆盖行为的顺序是不确定的,因此原始值的最终值也将无法确定。例如:

var x = 10
func f(inout a: Int, inout _ b: Int) {
    a += 1
    b += 10
}
f(&x, &x) // 编译报错 error: inout arguments are not allowed to alias each other

如果嵌套函数在外层函数返回后才调用,嵌套函数对输入输出参数造成的任何改变将不会影响到原始值。例如:

func outer(inout a: Int) -> () -> Void {
    func inner() {
        a += 1
    }
    return inner
}

var x = 10
let f = outer(&x)
f()
print(x)
// 打印 “10”

调用嵌套函数 inner()a 递增后,x 的值并未发生改变,因为 inner() 在外层函数 outer() 返回后才被调用。若要改变 x 的值,必须在 outer() 返回前调用 inner()

关于输入输出参数的详细讨论,请参阅 输入输出参数

特殊参数

参数可以被忽略,参数的数量可变,并且还可以提供默认值,使用形式如下:

_ : 参数类型
参数名称: 参数类型...
参数名称: 参数类型 = 默认参数值

以下划线(_)命名的参数是被显式忽略的,无法在函数体内使用。

一个参数的基础类型名称如果紧跟着三个点(...),会被视为可变参数。一个函数至多可以拥有一个可变参数,且必须是最后一个参数。可变参数会作为该参数类型的数组。举例来讲,可变参数 Int... 被看作 [Int]。关于使用可变参数的例子,请参阅 可变参数

如果在参数类型后面有一个以等号(=)连接的表达式,该参数会拥有默认值,即给定表达式的值。当函数被调用时,给定的表达式会被求值。如果参数在函数调用时被省略了,就会使用其默认值。

func f(x: Int = 42) -> Int { return x }
f()     // 有效,使用默认值
f(7)    // 有效,提供了值
f(x: 7) // 无效,该参数没有外部名称

特殊方法

枚举或结构体的方法如果会修改 self 属性,必须以 mutating 声明修饰符标记。

子类重写超类中的方法必须以 override 声明修饰符标记。重写方法时不使用 override 修饰符,或者使用了 override 修饰符却并没有重写超类方法,都会产生一个编译时错误。

枚举或者结构体中的类型方法,要以 static 声明修饰符标记,而对于类中的类型方法,除了使用 static,也可使用 class 声明修饰符标记。

柯里化函数

可以将一个带有多个参数的函数重写为一个等价的函数,这个重写后的函数只有一个参数并且返回一个函数。这个返回的函数接受下一个参数,并且返回另外一个函数。如此一直持续到再没有剩余的参数,最终返回的函数返回最初的多参函数要返回的原始值。这种重写的函数被称为柯里化函数。例如,可以为 addTwoInts(a:b:) 函数重写一个等价的 addTwoIntsCurried(a:)(b:) 函数:

func addTwoInts(a a: Int, b: Int) -> Int {
    return a + b
}

func addTwoIntsCurried(a a: Int) -> (Int -> Int) {
    func addTheOtherInt(b: Int) -> Int {
        return a + b
    }
    return addTheOtherInt
}

addTwoInts(a:b:) 函数接受两个整型值并且返回它们的和。addTwoIntsCurried(a:)(b:) 函数接受一个整型值,并且返回另外一个接受第二个整型值的函数,该函数会将其参数和第一个整型值相加。(这个嵌套函数会从外层函数中捕获第一个整型值)。

在 Swift 中,可以通过以下语法非常简明地编写一个柯里化函数:

func 函数名称(参数)(参数) -> 返回类型 {  
    语句 
}

举例来说,下面的两个声明是等价的:

func addTwoIntsCurried(a a: Int)(b: Int) -> Int {
    return a + b
}

func addTwoIntsCurried(a a: Int) -> (Int -> Int) {
    func addTheOtherInt(b: Int) -> Int {
        return a + b
    }
    return addTheOtherInt
}

为了像使用非柯里化函数一样的方式使用 addTwoIntsCurried(a:)(b:) 函数,必须用第一个整型参数调用 addTwoIntsCurried(a:)(b:),紧接着用第二个整型参数调用其返回的函数:

addTwoInts(a: 4, b: 5)
// 返回值为 9
addTwoIntsCurried(a: 4)(b: 5)
// 返回值为 9

调用一个非柯里化函数时必须一次性提供所有的参数,而使用函数的柯里化形式则可以分多次提供参数,每次调用提供一个(甚至可以在代码中的不同地方)。这被称为偏函数应用。例如,可以为 addTwoIntsCurried(a:)(b:) 函数提供整形参数 1,然后把返回的函数赋值给常量 plusOne

let plusOne = addTwoIntsCurried(a: 1)
// plusOne 是类型为 Int -> Int 的函数

因为 plusOne 是函数 addTwoIntsCurried(a:)(b:) 的首个参数为 1 时返回的函数,所以可以调用 plusOne 并传入一个整型值使其和 1 相加。

plusOne(b: 10)
// 返回值为 11

抛出错误的函数和方法

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

func 函数名称(参数列表) throws -> 返回类型 {
    语句
}

抛出函数或抛出方法的调用必须包裹在 try 或者 try! 表达式中(也就是说,在作用域内使用 try 或者 try! 运算符)。

throws 关键字是函数的类型的一部分,非抛出函数是抛出函数的子类型。所以,可以在使用抛出函数的地方使用非抛出函数。对于柯里化函数,throws 关键字仅应用于最内层的函数。

不能仅基于函数能否抛出错误来进行函数重载。也就是说,可以基于函数的函数类型的参数能否抛出错误来进行函数重载。

抛出方法不能重写非抛出方法,而且抛出方法不能满足协议对于非抛出方法的要求。也就是说,非抛出方法可以重写抛出方法,而且非抛出方法可以满足协议对于抛出方法的要求。

重新抛出错误的函数和方法

函数或方法可以使用 rethrows 关键字来声明,从而表明仅当该函数或方法的一个函数类型的参数抛出错误时,该函数或方法才抛出错误。这类函数和方法被称为重抛函数和重抛方法。重新抛出错误的函数或方法必须至少有一个参数的类型为抛出函数。

func functionWithCallback(callback: () throws -> Int) rethrows {
    try callback()
}

抛出方法不能重写重抛方法,而且抛出方法不能满足协议对于重抛方法的要求。也就是说,重抛方法可以重写抛出方法,而且重抛方法可以满足协议对于抛出方法的要求。

函数声明语法

函数声明函数头 函数名 泛型形参子句可选 函数签名 函数体可选

函数头特性列表可选 声明修饰符列表可选 func
函数名标识符 | 运算符

函数签名参数子句列表 throws可选 函数结果可选
函数签名参数子句列表 rethrows 函数结果可选
</a> 函数结果-> 特性列表可选 类型
函数体代码块

参数子句列表参数子句 参数子句列表可选
</a> 参数子句( ) | ( 参数列表 )
参数列表参数 | 参数 , 参数列表
</a> 参数let可选 外部参数名可选 内部参数名 类型标注 默认参数子句可选
参数var 外部参数名可选 内部参数名 类型标注 默认参数子句可选
参数inout 外部参数名可选 内部参数名 类型标注
参数外部参数名可选 内部参数名 类型标注 ...
外部参数名标识符 | _
</a> 内部参数名标识符 | _
默认参数子句= 表达式

枚举声明

在程序中使用枚举声明来引入一个枚举类型。

枚举声明有两种基本形式,使用关键字 enum 来声明。枚举声明体包含零个或多个值,称为枚举用例,还可包含任意数量的声明,包括计算型属性,实例方法,类型方法,构造器,类型别名,甚至其他枚举,结构体,和类。枚举声明不能包含析构器或者协议声明。

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

不同于类或者结构体,枚举类型并不提供隐式的默认构造器,所有构造器必须显式声明。一个构造器可以委托给枚举中的其他构造器,但是构造过程仅当构造器将一个枚举用例指定给 self 后才算完成。

和结构体类似但是和类不同,枚举是值类型。枚举实例在被赋值到变量或常量时,或者传递给函数作为参数时会被复制。更多关于值类型的信息,请参阅 结构体和枚举是值类型

可以扩展枚举类型,正如在 扩展声明 中讨论的一样。

包含任意类型用例的枚举

如下的形式声明了一个包含任意类型枚举用例的枚举变量:

enum 枚举名称: 采纳的协议 {  
    case 枚举用例1  
    case 枚举用例2(关联值类型)  
}

这种形式的枚举声明在其他语言中有时被叫做可识别联合。

在这种形式中,每个用例块由关键字 case 开始,后面紧接一个或多个以逗号分隔的枚举用例。每个用例名必须是独一无二的。每个用例也可以指定它所存储的指定类型的值,这些类型在关联值类型的元组中被指定,紧跟用例名之后。

具有关联值的枚举用例可以像函数一样使用,从而通过指定的关联值创建一个枚举实例。和真正的函数一样,你可以获取一个枚举用例的引用,然后在后续代码中调用它。

enum Number {
    case Integer(Int)
    case Real(Double)
}

// f 的类型为 (Int) -> Number
let f = Number.Integer

// 利用 f 把一个整数数组转成 Number 数组
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 修饰符标记的枚举类型可以既包含有关联值的用例,同时还可包含没有关联值的用例。但是,它不能再单独使用 indirect 修饰符来标记某个用例。

包含原始值类型的枚举

以下形式声明了一种枚举类型,其中各个枚举用例的类型均为同一种基本类型:

enum 枚举名称: 原始值类型, 采纳的协议 {  
    case 枚举用例1 = 原始值1
    case 枚举用例2 = 原始值2
}

在这种形式中,每一个用例块由 case 关键字开始,后面紧跟一个或多个以逗号分隔的枚举用例。和第一种形式的枚举用例不同,这种形式的枚举用例包含一个基础值,叫做原始值,各个枚举用例的原始值的类型必须相同。这些原始值的类型通过原始值类型指定,必须表示一个整数,浮点数,字符串,或者字符。原始值类型必须符合 Equatable 协议和下列字面量转换协议中的一种:整型字面量需符合 IntergerLiteralConvertible 协议,浮点型字面量需符合 FloatingPointLiteralConvertible 协议,包含任意数量字符的字符串型字面量需符合 StringLiteralConvertible 协议,仅包含一个单一字符的字符串型字面量需符合 ExtendedGraphemeClusterLiteralConvertible 协议。每一个用例的名字和原始值必须唯一。

如果原始值类型被指定为 Int,则不必为用例显式地指定原始值,它们会隐式地被赋值 012 等。每个未被赋值的 Int 类型的用例会被隐式地赋值,其值为上一个用例的原始值加 1

enum ExampleEnum: Int {
    case A, B, C = 5, D
}

在上面的例子中,ExampleEnum.A 的原始值是 0ExampleEnum.B 的原始值是 1。因为 ExampleEnum.C 的原始值被显式地设定为 5,因此 ExampleEnum.D 的原始值会自动增长为 6

如果原始值类型被指定为 String 类型,你不用明确地为用例指定原始值,每个没有指定原始值的用例会隐式地将用例名字作为原始值。

enum WeekendDay: String {
    case Saturday, Sunday
}

在上面这个例子中,WeekendDay.Saturday 的原始值是 "Saturday"WeekendDay.Sunday 的原始值是 "Sunday"

枚举用例具有原始值的枚举类型隐式地符合定义在 Swift 标准库中的 RawRepresentable 协议。所以,它们拥有一个 rawValue 属性和一个可失败构造器 init?(rawValue: RawValue)。可以使用 rawValue 属性去获取枚举用例的原始值,例如 ExampleEnum.B.rawValue。还可以根据原始值来获取一个相对应的枚举用例,只需调用枚举的可失败构造器,例如 ExampleEnum(rawValue: 5),这个可失败构造器返回一个可选类型的用例。要获得更多关于具有原始值的枚举用例的信息和例子,请参阅 原始值

访问枚举用例

使用点语法(.)来引用枚举类型的枚举用例,例如 EnumerationType.EnumerationCase。当枚举类型可以由上下文推断而出时,可以省略它(但是 . 仍然需要),正如 枚举语法显式成员表达式 所述。

可以使用 switch 语句来检验枚举用例的值,正如 使用 switch 语句匹配枚举值 所述。枚举类型是模式匹配的,依靠 switch 语句 case 块中的枚举用例模式,正如 枚举用例模式 所述。

枚举声明语法

枚举声明特性列表可选 访问级别修饰符可选 联合风格枚举
枚举声明特性列表可选 访问级别修饰符 可选 原始值风格枚举

联合风格枚举indirect可选 enum 枚举名称 泛型形参子句可选 类型继承子句可选 { 多个联合风格枚举成员可选 }
</a> 多个联合风格枚举成员联合风格枚举成员 多个联合风格枚举成员可选
联合风格枚举成员声明 | 联合风格枚举用例子句
</a> 联合风格枚举用例子句特性列表可选 indirect可选 case 联合风格枚举用例列表
联合风格枚举用例列表联合风格枚举用例 | 联合风格枚举用例 , 联合风格枚举用例列表
</a> 联合风格枚举用例枚举用例名称 元组类型可选
枚举名称标识符
枚举用例名称标识符

原始值风格枚举enum 枚举名称 泛型形参子句可选 类型继承子句 { 多个原始值风格枚举成员 }
</a> 多个原始值风格枚举成员原始值风格枚举成员 多个原始值风格枚举成员可选
原始值风格枚举成员声明 | 原始值风格枚举用例子句
</a> 原始值风格枚举用例子句特性列表可选 case 原始值风格枚举用例列表
原始值风格枚举用例列表原始值风格枚举用例 | 原始值风格枚举用例 , 原始值风格枚举用例列表
</a> 原始值风格枚举用例枚举用例名称 原始值赋值可选
原始值赋值= 原始值字面量
原始值字面量数字型字面量 | 字符串型字面量 | 布尔型字面量

结构体声明

使用结构体声明可以在程序中引入一个结构体类型。结构体声明使用 struct 关键字,遵循如下的形式:

struct 结构体名称: 采纳的协议 {  
    多条声明
}

结构体内可包含零个或多个声明。这些声明可以包括存储型和计算型属性,类型属性,实例方法,类型方法,构造器,下标,类型别名,甚至其他结构体,类,和枚举声明。结构体声明不能包含析构器或者协议声明。关于结构体的详细讨论和示例,请参阅 类和结构体

结构体可以采纳任意数量的协议,但是不能继承自类,枚举或者其他结构体。

有三种方法可以创建一个声明过的结构体实例:

  • 调用结构体内声明的构造器,正如 构造器 所述。

  • 如果没有声明构造器,调用结构体的成员逐一构造器,正如 结构体类型的成员逐一构造器 所述。

  • 如果没有声明构造器,而且结构体的所有属性都有初始值,调用结构体的默认构造器,正如 默认构造器 所述。

结构体的构造过程请参阅 构造过程

结构体实例的属性可以用点语法(.)来访问,正如 访问属性 所述。

结构体是值类型。结构体的实例在被赋予变量或常量,或传递给函数作为参数时会被复制。关于值类型的更多信息,请参阅 结构体和枚举是值类型

可以使用扩展声明来扩展结构体类型的行为,请参阅 扩展声明

结构体声明语法
</a> 结构体声明特性列表可选 访问级别修饰符 可选 struct 结构体名称 泛型形参子句可选 类型继承子句可选 结构体主体
结构体名称标识符
结构体主体{ 多条声明可选 }

类声明

可以在程序中使用类声明来引入一个类。类声明使用关键字 class,遵循如下的形式:

class 类名: 超类, 采纳的协议 {  
    多条声明  
}

类内可以包含零个或多个声明。这些声明可以包括存储型和计算型属性,实例方法,类型方法,构造器,析构器,下标,类型别名,甚至其他结构体,类,和枚举声明。类声明不能包含协议声明。关于类的详细讨论和示例,请参阅 类和结构体

一个类只能继承自一个超类,但是可以采纳任意数量的协议。超类紧跟在类名和冒号后面,其后跟着采纳的协议。泛型类可以继承自其它泛型类和非泛型类,但是非泛型类只能继承自其它非泛型类。当在冒号后面写泛型超类的名称时,必须写上泛型类的全名,包括它的泛型形参子句。

正如 构造器声明 所讨论的,类可以有指定构造器和便利构造器。类的指定构造器必须初始化类中声明的所有属性,并且必须在调用超类构造器之前。

类可以重写属性,方法,下标,以及构造器。重写的属性,方法,下标,和指定构造器必须以 override 声明修饰符标记。

为了要求子类去实现超类的构造器,使用 required 声明修饰符标记超类的构造器。子类实现超类构造器时也必须使用 required 声明修饰符。

虽然超类属性和方法声明可以被当前类继承,但是超类声明的指定构造器却不能。即便如此,如果当前类重写了超类的所有指定构造器,它就继承了超类的便利构造器。Swift 的类并不继承自一个通用基础类。

有两种方法来创建已声明的类的实例:

  • 调用类中声明的构造器,请参阅 构造器

  • 如果没有声明构造器,而且类的所有属性都被赋予了初始值,调用类的默认构造器,请参阅 默认构造器

类实例属性可以用点语法(.)来访问,请参阅 访问属性

类是引用类型。当被赋予常量或变量,或者传递给函数作为参数时,类的实例会被引用,而不是被复制。关于引用类型的更多信息,请参阅 结构体和枚举是值类型

可以使用扩展声明来扩展类的行为,请参阅 扩展声明

类声明语法
</a> 类声明特性列表可选 访问级别修饰符可选 class 类名 泛型形参子句可选 类型继承子句可选 类主体
类名标识符
类主体{ 多条声明可选 }

协议声明

协议声明可以为程序引入一个命名的协议类型。协议声明只能在全局区域使用 protocol 关键字来进行声明,并遵循如下形式:

protocol 协议名称: 继承的协议 {  
    协议成员声明
}

协议的主体包含零个或多个协议成员声明,这些成员描述了任何采纳该协议的类型必须满足的一致性要求。一个协议可以声明采纳者必须实现的某些属性、方法、构造器以及下标。协议也可以声明特殊类型的类型别名,叫做关联类型,它可以指定协议的不同声明之间的关系。协议声明不能包括类,结构体,枚举或者其它协议的声明。协议成员声明会在后面进行讨论。

协议类型可以继承自任意数量的其它协议。当一个协议类型继承自其它协议的时候,来自其它协议的所有要求会聚合在一起,而且采纳当前协议的类型必须符合所有的这些要求。关于如何使用协议继承的例子,请参阅 协议继承

注意
也可以使用协议合成类型来集合多个协议的一致性要求,请参阅 协议合成类型协议合成

可以通过类型的扩展声明来采纳协议,从而为之前声明的类型添加协议一致性。在扩展中,必须实现所有采纳协议的要求。如果该类型已经实现了所有的要求,可以让这个扩展声明的主体留空。

默认地,符合某个协议的类型必须实现所有在协议中声明的属性、方法和下标。即便如此,可以用 optional 声明修饰符标注协议成员声明,以指定它们的实现是可选的。optional 修饰符仅仅可以用于使用 objc 特性标记过的协议。因此,仅仅类类型可以采用并符合包含可选成员要求的协议。更多关于如何使用 optional 声明修饰符的信息,以及如何访问可选协议成员的指导——例如不能确定采纳协议的类型是否实现了它们时——请参阅 可选协议要求

为了限制协议只能被类类型采纳,需要使用 class 关键字来标记协议,将 class 关键在写在冒号后面的继承的协议列表的首位。例如,下面的协议只能被类类型采纳:

protocol SomeProtocol: class {
    /* 这里是协议成员 */
}

任何继承自标记有 class 关键字的协议的协议也仅能被类类型采纳。

注意
如果协议已经用 objc 特性标记了,class 要求就隐式地应用于该协议,无需显式使用 class 关键字。

协议类型是命名的类型,因此它们可以像其他命名类型一样使用,正如 协议作为类型 所讨论的。然而,不能构造一个协议的实例,因为协议实际上不提供它们指定的要求的实现。

可以使用协议来声明作为代理的类或者结构体应该实现的方法,正如 委托(代理)模式 中所述。

协议声明语法

协议声明特性列表可选 访问级别修饰符可选 protocol 协议名称 类型继承子句可选 协议主体
</a> 协议名称标识符
协议主体{ 协议成员声明列表可选 }

协议成员声明协议属性声明
协议成员声明协议方法声明
协议成员声明协议构造器声明
协议成员声明协议下标声明
协议成员声明协议关联类型声明
协议成员声明列表协议成员声明 协议成员声明列表可选

协议属性声明

协议可以通过在协议声明主体中引入一个协议属性声明,来声明符合的类型必须实现的属性。协议属性声明有一种特殊的变量声明形式:

var 属性名: 类型 { get set }

同其它协议成员声明一样,这些属性声明仅仅针对符合该协议的类型声明了 getter 和 setter 要求,你不能在协议中直接实现 getter 和 setter。

符合类型可以通过多种方式满足 getter 和 setter 要求。如果属性声明包含 getset 关键字,符合类型就可以用可读写(实现了 getter 和 setter)的存储型变量属性或计算型属性来满足此要求,但是属性不能以常量属性或只读计算型属性实现。如果属性声明仅仅包含 get 关键字的话,它可以作为任意类型的属性被实现。关于如何实现协议中的属性要求的例子,请参阅 属性要求

另请参阅 变量声明

协议属性声明语法
协议属性声明变量声明头 变量名称 类型标注 getter-setter关键字代码块

协议方法声明

协议可以通过在协议声明主体中引入一个协议方法声明,来声明符合的类型必须实现的方法。协议方法声明和函数方法声明有着相同的形式,但有两项例外:它们不包括函数体,也不能包含默认参数。关于如何实现协议中的方法要求的例子,请参阅 方法要求

使用 static 声明修饰符可以在协议声明中声明一个静态方法。结构体实现这些方法时使用 static 声明修饰符,类在实现这些方法时,除了使用 static 声明修饰符,还可以选择使用 class 声明修饰符。通过扩展实现时亦是如此。

另请参阅 函数声明

协议方法声明语法
协议方法声明函数头 函数名 泛型形参子句可选 函数签名

协议构造器声明

协议可以通过在协议声明主体中引入一个协议构造器声明,来声明符合的类型必须实现的构造器。协议构造器声明 除了不包含实现主体外,和构造器声明有着相同的形式。

符合类型可以通过实现一个非可失败构造器或者 init! 可失败构造器来满足一个非可失败协议构造器的要求,可以通过实现任意类型的构造器来满足一个可失败协议构造器的要求。

类在实现一个构造器去满足一个协议的构造器要求时,如果这个类还没有用 final 声明修饰符标记,这个构造器必须用 required 声明修饰符标记。

另请参阅 构造器声明

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

协议下标声明

协议可以通过在协议声明主体中引入一个协议下标声明,来声明符合的类型必须实现的下标。协议下标声明有一个特殊的下标声明形式:

subscript (参数列表) -> 返回类型 { get set }

下标声明只为符合类型声明了 getter 和 setter 要求。如果下标声明包含 getset 关键字,符合类型也必须实现 getter 和 setter 子句。如果下标声明只包含 get 关键字,符合类型必须实现 getter 子句,可以选择是否实现 setter 子句。

另请参阅 下标声明

协议下标声明语法
协议下标声明下标头 下标结果 getter-setter关键字代码块

协议关联类型声明

使用关键字 typealias 来声明协议关联类型。关联类型为作为协议声明的一部分,为某种类型提供了一个别名。关联类型和泛型参数子句中的类型参数很相似,但是它们和 Self 一样,用于协议中。Self 指代采纳协议的类型。要获得更多信息和例子,请参阅 关联类型

另请参阅 类型别名声明

协议关联类型声明语法
协议关联类型声明类型别名头 类型继承子句可选 类型别名赋值可选

构造器声明

构造器声明会为程序中的类,结构体或枚举引入构造器。构造器使用关键字 init 来声明,有两种基本形式。

结构体,枚举,类可以有任意数量的构造器,但是类的构造器具有不同的规则和行为。不同于结构体和枚举,类有两种构造器,即指定构造器和便利构造器,请参阅 构造过程

采用如下形式声明结构体和枚举的构造器,以及类的指定构造器:

init(参数列表) {  
    构造语句
}

类的指定构造器直接将类的所有属性初始化。它不能调用类中的其他构造器,如果类有超类,它还必须调用超类的一个指定构造器。如果该类从它的超类继承了属性,必须在调用超类的指定构造器后才能修改这些属性。

指定构造器只能在类声明的上下文中声明,不能在扩展声明中声明。

结构体和枚举的构造器可以调用其他已声明的构造器,委托其他构造器来进行部分或者全部构造过程。

要为类声明一个便利构造器,用 convenience 声明修饰符来标记构造器声明:

convenience init(参数列表) {  
    构造语句
}

便利构造器可以将构造过程委托给另一个便利构造器或一个指定构造器。但是,类的构造过程必须以一个将类中所有属性完全初始化的指定构造器的调用作为结束。便利构造器不能调用超类的构造器。

可以使用 required 声明修饰符,将便利构造器和指定构造器标记为每个子类都必须实现的构造器。这种构造器的子类实现也必须使用 required 声明修饰符标记。

默认情况下,超类中的构造器不会被子类继承。但是,如果子类的所有存储型属性都有默认值,而且子类自身没有定义任何构造器,它将继承超类的构造器。如果子类重写了超类的所有指定构造器,子类将继承超类的所有便利构造器。

和方法,属性和下标一样,需要使用 override 声明修饰符标记重写的指定构造器。

注意
如果使用 required 声明修饰符标记一个构造器,在子类中重写这种构造器时,无需使用 override 修饰符。

就像函数和方法,构造器也可以抛出或者重抛出错误,你可以在构造器参数列表的圆括号之后使用 throwsrethrows 关键字来表明相应的抛出行为。

关于在不同类型中声明构造器的例子,请参阅 构造过程

可失败构造器

可失败构造器可以生成所属类型的可选实例或者隐式解包可选实例,因此,这种构造器通过返回 nil 来指明构造过程失败。

声明生成可选实例的可失败构造器时,在构造器声明的 init 关键字后加追加一个问号(init?)。声明生成隐式解包可选实例的可失败构造器时,在构造器声明后追加一个叹号(init!)。使用 init? 可失败构造器生成结构体的一个可选实例的例子如下。

struct SomeStruct {
    let string: String
    //生成一个 SomeStruct 的可选实例
    init?(input: String) {
        if input.isEmpty {
            // 丢弃 self,并返回 nil
            return nil
        }
        string = input
    }
}

调用 init? 可失败构造器和调用非可失败构造器的方式相同,不过你需要处理可选类型的返回值。

if let actualInstance = SomeStruct(input: "Hello") {
    // 利用 SomeStruct 实例做些事情
} else {
    // SomeStruct 实例的构造过程失败,构造器返回了 nil
}

结构体或者枚举的可失败构造器可以在构造器实现中的任意位置返回 nil。然而,对于类的可失败构造器,仅在类的所有存储属性被初始化之后,并且 self.initsuper.init 被调用之后(就是说,构造器委托过程完成后)才能返回 nil

可失败构造器可以委托任意种类的构造器。非可失败可以委托其它非可失败构造器或者 init! 可失败构造器。非可失败构造器可以委托超类的 init? 可失败指定构造器,但是需要使用强制解包,例如 super.init()!

构造过程的失败通过构造器委托来传递。具体来说,如果可失败构造器委托的可失败构造器构造过程失败并返回 nil,那么该可失败构造器也会构造失败并隐式地返回 nil。如果非可失败构造器委托的 init! 可失败构造器构造失败并返回了 nil,那么会发生运行时错误(如同使用 ! 操作符去强制解包一个值为 nil 的可选值)。

子类可以用任意种类的指定构造器重写超类的可失败指定构造器,但是只能用非可失败指定构造器重写超类的非可失败指定构造器。

更多关于可失败构造器的信息和例子,请参阅 可失败构造器

构造器声明语法
</a> 构造器声明构造器头 泛型形参子句可选 参数子句 throws可选 构造器主体
构造器声明构造器头 泛型形参子句可选 参数子句 rethrows可选 构造器主体
构造器头特性列表可选 声明修饰符列表可选 init
构造器头特性列表可选 声明修饰符列表可选 init ?
构造器头特性列表可选 声明修饰符列表可选 init !
构造器主体代码块

析构器声明

析构器声明可以为类声明一个析构器。析构器没有参数,遵循如下格式:

deinit {  
    语句
}

当没有任何强引用引用着类的对象,对象即将被释放时,析构器会被自动调用。析构器只能在类主体的声明中声明,不能在类的扩展声明中声明,并且每个类最多只能有一个析构器。

子类会继承超类的析构器,并会在子类对象将要被释放时隐式调用。继承链上的所有析构器全部调用完毕后子类对象才会被释放。

析构器不能直接调用。

关于如何在类声明中使用析构器的例子,请参阅 析构过程

析构器声明语法
析构器声明特性列表可选 deinit 代码块

扩展声明

扩展声明可以扩展一个现存的类,结构体,和枚举类型的行为。扩展声明使用关键字 extension,遵循如下格式:

extension 类型名称: 采纳的协议 {  
    声明语句
}

扩展声明体可包含零个或多个声明语句。这些声明语句可以包括计算型属性,计算型类型属性,实例方法,类型方法,构造器,下标声明,甚至其他结构体,类,和枚举声明。扩展声明不能包含析构器,协议声明,存储型属性,属性观察器,或其他扩展声明。关于扩展声明的详细讨论,以及各种扩展声明的例子,请参阅 扩展

扩展声明可以为现存的类,结构体,枚举添加协议一致性,但是不能为类添加超类,因此,在扩展声明的类型名称的冒号后面仅能指定一个协议列表。

现存类型的属性,方法,构造器不能在扩展中被重写。

扩展声明可以包含构造器声明。这意味着,如果被扩展的类型在其他模块中定义,构造器声明必须委托另一个在那个模块中声明的构造器,以此确保该类型能被正确地初始化。

扩展声明语法
</a> 扩展声明访问级别修饰符可选 extension 类型标识符 类型继承子句可选 扩展主体
扩展主体{ 多条声明可选 }

下标声明

下标声明用于为特定类型的对象添加下标支持,通常也用于为访问集合,列表和序列中的元素提供语法便利。下标声明使用关键字 subscript,形式如下:

subscript (参数列表) -> 返回类型 {
    get {
        语句
    }
    set(setter 名称) {
        语句
    }
}

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

参数列表指定一个或多个用于在相关类型的下标表达式中访问元素的索引(例如,表达式 object[i] 中的 i)。索引可以是任意类型,但是必须包含类型标注。返回类型指定被访问的元素的类型。

和计算型属性一样,下标声明支持对元素的读写操作。getter 用于读取值,setter 用于写入值。setter 子句是可选的,当仅需要一个 getter 子句时,可以将二者都忽略,直接返回请求的值即可。但是,如果提供了 setter 子句,就必须提供 getter 子句。

圆括号以及其中的 setter 名称是可选的。如果提供了 setter 名称,它会作为 setter 的参数名称。如果不提供 setter 名称,那么 setter 的参数名称默认是 value。setter 名称的类型必须与返回类型相同。

可以重载下标,只要参数列表或返回类型与先前不同即可。还可以重写继承自超类的下标,此时必须使用 override 声明修饰符声明被重写的下标。

同样可以在协议声明的上下文中声明下标,正如 协议下标声明 中所述。

更多关于下标的信息和例子,请参阅 下标

下标声明语法
</a> 下标声明下标头 下标结果 代码块
下标声明下标头 下标结果 getter-setter代码块
下标声明下标头 下标结果 getter-setter关键字代码块
下标头特性列表可选 声明修饰符列表可选 subscript 参数子句
下标结果-> 特性列表可选 类型

运算符声明

运算符声明会向程序中引入中缀、前缀或后缀运算符,使用关键字 operator 来声明。

可以声明三种不同的缀性:中缀、前缀和后缀。运算符的缀性指定了运算符与其运算对象的相对位置。

运算符声明有三种基本形式,每种缀性各一种。运算符的缀性通过在 operator 关键字之前添加声明修饰符 infixprefixpostfix 来指定。每种形式中,运算符的名字只能包含 运算符 中定义的运算符字符。

下面的形式声明了一个新的中缀运算符:

infix operator 运算符名称 {
    precedence 优先级
    associativity 结合性
}

中缀运算符是二元运算符,置于两个运算对象之间,例如加法运算符(+)位于表达式 1 + 2 的中间。

中缀运算符可以选择指定优先级或结合性,或者两者同时指定。

运算符的优先级可以指定在没有括号包围的情况下,运算符与其运算对象如何结合。可以使用上下文相关的关键字 precedence 以及紧随其后的优先级数字来指定一个运算符的优先级。优先级可以是 0255 之间的任何十进制整数。与十进制整数字面量不同的是,它不可以包含任何下划线字符。尽管优先级是一个特定的数字,但它仅用作与另一个运算符的优先级比较大小。也就是说,当两个运算符为同一个运算对象竞争时,例如 2 + 3 * 5,优先级更高的运算符将优先参与运算。

运算符的结合性可以指定在没有括号包围的情况下,多个优先级相同的运算符将如何组合。可以使用上下文相关的关键字 associativity 以及紧随其后的结合性关键字来指定运算符的结合性,结合性关键字也是上下文相关的,包括 leftright,和 none。左结合运算符以从左到右的顺序进行组合。例如,减法运算符(-)具有左结合性,因此 4 - 5 - 6(4 - 5) - 6 的形式组合,其结果为 -7。右结合运算符以从右到左的顺序组合,而设置为 none 的运算符不进行组合。具有相同优先级的非结合运算符,不可以互相邻接。例如,表达式 1 < 2 < 3 是非法的。

声明时不指定任何优先级或结合性的中缀运算符,优先级为 100,结合性为 none

下面的形式声明了一个新的前缀运算符:

prefix operator 运算符名称 {}

出现在运算对象前边的前缀运算符是一元运算符,例如表达式 ++i 中的前缀递增运算符(++)。

前缀运算符的声明中不指定优先级,而且前缀运算符是非结合的。

下面的形式声明了一个新的后缀运算符:

postfix operator 运算符名称 {}

紧跟在运算对象后边的后缀运算符是一元运算符,例如表达式 i++ 中的后缀递增运算符(++)。

和前缀运算符一样,后缀运算符的声明中不指定优先级,而且后缀运算符是非结合的。

声明了一个新的运算符以后,需要实现一个跟这个运算符同名的函数来实现这个运算符。如果是实现一个前缀或者后缀运算符,也必须使用相符的 prefix 或者 postfix 声明修饰符标记函数声明。如果是实现中缀运算符,则不需要使用 infix 声明修饰符标记函数声明。关于如何实现一个新的运算符的例子,请参阅 自定义运算符

运算符声明语法

运算符声明前缀运算符声明 | 后缀运算符声明 | 中缀运算符声明

前缀运算符声明prefix 运算符 运算符 { }
</a> 后缀运算符声明postfix 运算符 运算符 { }
中缀运算符声明infix 运算符 运算符 { 中缀运算符属性可选 }

中缀运算符属性优先级子句可选 结和性子句可选
</a> 优先级子句precedence 优先级水平
优先级水平 → 十进制整数 0 到 255,包含 0 和 255
</a> 结和性子句associativity 结和性
结和性left | right | none

声明修饰符

声明修饰符都是关键字或上下文相关的关键字,可以修改一个声明的行为或者含义。可以在声明的特性(如果存在)和引入该声明的关键字之间,利用声明修饰符的关键字或上下文相关的关键字指定一个声明修饰符。

dynamic

该修饰符用于修饰任何兼容 Objective-C 的类的成员。访问被 dynamic 修饰符标记的类成员将总是由 Objective-C 运行时系统进行动态派发,而不会由编译器进行内联或消虚拟化。

因为被标记 dynamic 修饰符的类成员会由 Objective-C 运行时系统进行动态派发,所以它们会被隐式标记 objc 特性。

final

该修饰符用于修饰类或类中的属性,方法,以及下标。如果用它修饰一个类,那么这个类不能被继承。如果用它修饰类中的属性,方法或下标,那么它们不能在子类中被重写。

lazy

该修饰符用于修饰类或结构体中的存储型变量属性,表示该属性的初始值最多只被计算和存储一次,且发生在它被第一次访问时。关于如何使用 lazy 修饰符的例子,请参阅 惰性存储型属性

optional

该修饰符用于修饰协议中的属性,方法,以及下标成员,表示符合类型不必实现这些成员要求。

只能将 optional 修饰符用于被 objc 特性标记的协议。这样一来,就只有类类型可以采纳并符合拥有可选成员要求的协议。关于如何使用 optional 修饰符,以及如何访问可选协议成员(比如,不确定符合类型是否已经实现了这些可选成员)的信息,请参阅 可选协议要求

required

该修饰符用于修饰类的指定构造器或便利构造器,表示该类所有的子类都必须实现该构造器。在子类实现该构造器时,必须同样使用 required 修饰符修饰该构造器。

weak

该修饰符用于修饰变量或存储型变量属性,表示该变量或属性持有其存储的对象的弱引用。这种变量或属性的类型必须是可选的类类型。使用 weak 修饰符可避免强引用循环。关于 weak 修饰符的更多信息和例子,请参阅 弱引用

访问控制级别

Swift 提供了三个级别的访问控制:publicinternalprivate。可以使用以下任意一种访问级别修饰符来指定声明的访问级别。访问控制在 访问控制 中有详细讨论。

public

该修饰符表示声明可被同模块的代码访问,只要其他模块导入了声明所在的模块,也可以进行访问。

internal

该修饰符表示声明只能被同模块的代码访问。默认情况下,绝大多数声明会被隐式标记 internal 访问级别修饰符。

private

该修饰符表示声明只能被所在源文件的代码访问。

以上访问级别修饰符都可以选择带上一个参数,该参数由一对圆括号和其中的 set 关键字组成(例如,private(set))。使用这种形式的访问级别修饰符来限制某个属性或下标的 setter 的访问级别低于其本身的访问级别,正如 Getter 和 Setter 中所讨论的。

声明修饰符的语法

声明修饰符class | convenience| dynamic | final | infix | lazy | mutating | nonmutating | optional | override | postfix | prefix | required | static | unowned | unowned ( safe ) | unowned ( unsafe ) | weak
声明修饰符 → 访问级别修饰符
声明修饰符列表声明修饰符 声明修饰符列表可选

访问级别修饰符 → internal | internal ( set )
访问级别修饰符 → private | private ( set )
访问级别修饰符 → public | public ( set )