访问控制
访问控制限制其他源文件或者模块中的代码访问你的代码。这个特性使得你可以隐藏你的代码的具体实现,还可以指定外部代码能够访问和使用的接口。
可以给个体类型(类、结构体和枚举)分配访问级别,同样也可以对属于这些类型的属性、方法、构造方法和下标进行同样的操作。 Protocols can be restricted to a certain context,as can global constants,variables,and functions.
除了提供访问控制的多种级别,Swfit通过提供默认访问级别来限制需要指定明确访问级别的需要。实际上,如果你写的是一个单一目的app,你可能压根就不需要指定访问控制级别。
NOTE
The varIoUs aspects of your code that can have access control applied to them (properties,types,functions,and so on) are referred to as “entities” in the sections below,for brevity.
为了简洁,代码中可以应用访问控制内容(属性、类型、函数等等)在后面的内容中被称作“实体对象”。
模块和源文件
Swfit的访问控制模型是基于模块和源文件概念的。
一个模块(module)是一个代码单元——一个框架或者一个应用被作为一个单元构建和组装,用Swfit的关键字import可以导入一个模块到另外一个模块。
在Xcode中的每个构造目标(比如一个app集或者框架)在Swfit中被当作一个单独模块处理。如果你将你的app的代码归集在一起作为一个标准独立框架——可能为了封装和复用来自多个应用的代码——然后你在框架中定义的所有内容都会作为一个单独模块,当它被一个app导入和使用时,或者当它被另外一个框架使用时。
一个源文件(source file)是一个模块中的一个单独的Swift源代码文件(实际上是app或者框架中的单独文件)。尽管在单一源代码文件中定义单个类型是比较普遍的,一个单一源代码文件也可以包含多个类型、函数等等。
访问级别
Swift提供了三种访问级别。这些访问级别和源文件(实体对象在其中定义)有关,也和源文件所属的模块有关。
Public能让实体对象在其定义模块中的任何源文件被使用,在导入了定义模块的其他模块的源文件中也可以被使用。通常在指定你给一个框架的公共接口时使用Public。
Internal能让实体对象在其定义模块中的任何源文件被使用,在模块之外不行。通常在定义一个app或者框架的内部结构时使用Internal。
Private 限制只能在实体对象的定义文件使用它们。通常用private隐藏特定功能的具体实现。
Public是最高(最低限制)的访问级别,Private是最低(最高限制)的访问级别。
访问级别的指导原则
以下展示了Swift中访问级别的整体指导原则:没有一个实体对象能够依据比它自身更低的访问级别(更严格的限制)的实体对象被定义。(No entity can be defined in terms of another entity that has a lower (more restrictive) access level)(译者:实体对象所依赖的其他实体对象的访问级别一定比较高。)
比如:
一个公有的变量不能不被定义为是一个内部或者私有的类型,因为当那个变量在使用时,它的类型是无效的。
一个函数不能的访问级别不能高于它的参数类型和返回类型的访问级别,这是因为函数可以在它的参数类型是不可用的情况下被调用。
下文会涵盖针对Swfit语言的不同方面的这一指导原则的具体含义。
默认访问级别
所有在你的代码(有一个小例外,本章会在后面有描述)有一个默认的访问级别:internal,前提是你没有指定访问级别。作为一个结果,多数情况下你在代码中不需要指定访问级别。
单一目标app的访问级别(Access Levels for Single-Target Apps)
当你系莪一个单一目标app时,你的代码是在app内部独立的,它们不需要在app的模块之外是有效的。默认的访问级别internal足够满足这时的需要了。但是,你需要指定自己的访问级别。也许你为了对app模块内的其他代码隐藏实现细节,想要用Private标记想要隐藏的代码。
框架的访问级别
当开发一个框架时,将那些公开的接口标记为Public,这样它们能够被其他模块发现和访问,比如一个倒入了这个框架的app就可以访问框架的公开接口。这些开放的接口是框架的应用程序接口(或者说API)。
NOTE
框架的任何内部的实现细节可以使用默认访问级别internal,或者在你需要对框架内的其他代码隐藏它们时用pirvate标记它们。当需要将一个实体对象作为框架的API一部分时,就将它标记为public。
访问控制语法
给一个实体对象添加访问级别,是在实体对象的引导符前添加pulblic、internal或者private的其中之一:
public class SomePublicClass {} internal class SomeInternalClass {} private class SomePrivateClass {} public var somePublicVariable = 0 internal let someInternalConstant = 0 private func somePrivateFunction() {}
除非另外指定,默认的访问级别是internal,就像 默认访问级别(Default Access Levels)描述的一样。这意味着SomeInternalClass 和someInternalConstant 可以不用指定访问级别,它们依然会有internal的访问级别:
class SomeInternalClass {} // implicitly internal var someInternalConstant = 0 // implicitly internal
定制类型
如果想要给一个自定义类型指定一个明确的访问级别,在定义的时候就要做些什么了。新的类型可以在其访问级别允许范围内被使用,如果定义了一个私有类,那么它只能在定义它的源文件中作为一个属性的类型、一个函数的参数的类型或是一个返回值的类型被使用。
类型的访问控制级别同时会影响默那个类型成员(它的属性、方法、构造方法和下标)的默认访问级别。如果定义一个类型的访问级别是private的,那么它成员的默认访问级别就都是pirvate的了。如果定义一个类型的访问级别是internal或者是public的(或者没有指定访问级别使用了默认访问级别),那么类型成员的默认访问级别是internal。
NOTE
就像前面提到的,一个public的类型的成员默认是internal而非public。如果想要一个类型的成员是public的,需要明确指定。This requirement ensures that the public-facing API for a type is something you opt in to publishing,and avoids presenting the internal workings of a type as public API by mistake.
public class SomePublicClass { // explicitly public class public var somePublicProperty = 0 // explicitly public class member var someInternalProperty = 0 // implicitly internal class member private func somePrivateMethod() {} // explicitly private class member } class SomeInternalClass { // implicitly internal class var someInternalProperty = 0 // implicitly internal class member private func somePrivateMethod() {} // explicitly private class member } private class SomePrivateClass { // explicitly private class var somePrivateProperty = 0 // implicitly private class member func somePrivateMethod() {} // implicitly private class member }
元组类型
元组的访问级别是其中所有类型访问级别最严格的。比如,将俩个不同类型组合为一个元组,其中一个类型是initernal的另一个是private的,那么元组的访问级别将会是private的。
NOTE
元组类型没有像类、结构体、枚举和函数一样有标准的定义。一个元组的访问级别会在被使用时自动推测出来,不需要指定。
函数类型
函数的访问级别通过函数参数类型的访问级别和返回类型的访问级别计算得来,而且会取其中最严格的。如果计算得到的访问级别和上下文环境默认的访问级别不符,那么需要在函数定义中指定一个特定的访问级别。
下面的例子定义了一个全局函数叫做someFunction,函数自己没有指定访问级别。也许你期待这个函数有默认的访问级别“internal”,但实际上恰恰不是。实际上,someFunction写成下面这样不能通过编译:
func someFunction() -> (SomeInternalClass,SomePrivateClass) { // function implementation goes here }
这个函数的返回类型是一个元组,元组由前文 自定义类型(Custom Type)中定义的两个自定义类组成。其中之一的类型是“internal”,另外一个是“private”。因此整体元组的访问级别是“private”(元组中所有类型最低的访问级别)。
因为函数的返回类型是private的,所以为了函数的声明是有效的,必须标记函数的整体访问级别是private:
private func someFunction() -> (SomeInternalClass,SomePrivateClass) { // function implementation goes here }
用public 或者internal标示符标记someFunction是非法的,使用默认的internal也是一样,因为函数的public和internal使用者可能不能访问函数返回类型的private类。
枚举类型
枚举的独立分支(individual cases)自动的访问级别和其所处枚举的访问级别一样。也可以给枚举的单独分支指定范文级别。
下面的例子中,CompassPoint枚举有一个明确的访问类型“public”。枚举的分支 North、South、East和West同样有“public”的访问级别:
public enum CompassPoint { case North case South case East case West }
最初值和组合值
枚举定义中用到的初始值和组合值的类型,不能低于枚举的访问级别。比如,你不能在一个internal访问等级的枚举中使用一个private级别的初始值类型。
嵌套类型
在一个privtate类型中定义的嵌套类型自动获得private的访问级别。在public或者internal类型中定义的嵌套了性自动获得internal的访问级别。如果想要在一个public类型定义的嵌套类型是public的,需要明确指定嵌套类型是public的。
子类型
当前访问上下文环境中所有能访问的类,都可以构造它们的子类型。字类型不能比超类型的访问级别还高——比如,一个internal的超类,不能有一个public的子类型。
另外,可以重写当前访问上下文中能访问的任意类型的成员(方法、属性、构造方法或是下标)。
重写能让继承来的类成员比超类中的有更高的访问级别。下面的例子,类A是一个public类,他有一个private的方法叫做someMethod。类B是类A的子类,它的访问级别是比A低的“internal”。虽然如此,类B提供了一个对someMethod重写的版本,这个版本的访问级别是“internal”,这比超类中的实现访问级别高:
public class A { private func someMethod() {} } internal class B: A { override internal func someMethod() {} }
甚至,子类成员调用比其访问级别更低的超类成员也是允许的,只要这种调用超类成员的行为发生在访问级别上下文环境中允许的范围内就行(that is,within the same source file as the superclass for a private member call,or within the same module as the superclass for an internal member call):
public class A { private func someMethod() {} } internal class B: A { override internal func someMethod() { super.someMethod() } }
因为超类A和子类B定义在同一源文件中,所以B实现someMethod时调用super.someMethod()是合法的。
常量、变量、属性和下标
一个常量、变量或者属性不能够比它所在的类型访问级别高。例如,给一个私有类型定义共有属性是非法的。类似的,一个下标也不能比它的索引类型或者返回类型访问级别高。
如果一个常量、变量、属性或者下标采用了一个私有类型,那么它们必须被标记为private:
private var privateInstance = SomePrivateClass()
getter和setter
常量、变量、属性和下标的getter和setter自动获得它们所在内容的访问级别。
可以给setter设置比对应的getter更低的访问级别,来限制可读写的变量、属性或下标。在var或者subscript引导符前写上private(set)或者internal(set),可以分配更低的访问级别。
NOTE
这个规则适用于存储属性和计算属性。尽管不会给一个存储属性写显式的getter和setter,Swfit仍然会了给你提供访问存储属性而合成隐式的getter和setter。使用private(set)和internal(set)来显式的改变这样的合成setter,就像计算属性那样。
下面的例子定义了一个结构体叫做TrackedString,它会跟踪一个字符串属性被修改的次数:
struct TrackedString { private(set) var numberOfEdits = 0 var value: String = "" { didSet { numberOfEdits++ } } }
TrackedString结构体定义了一个字符串类型的存储属性叫做value,它有一个初始值“”(一个空字符串)。这个结构体同时定义了一个整型的存储属性叫做numberOfEdits,它被用来跟踪value被修改的次数。这个修改追踪是通过value属性上的一个didSet属性观察者实现的,每当value属性被设置为一个新值就会给numberOfEdits加1.
TrackedString结构体和value属性没有提供显式的访问控制级别,所以它们会获得默认的访问级别internal。但是,numberOfEdits属性的访问级别被标记为private(set),这样表明了这个属性将会只在TrackedString结构体定义的源文件范围内是可写的。这个属性的getter仍然是默认的访问级别internal,但是它的setter现在是private的。这样使得TrackedString 结构体在其内部修改numberOfEdits属性,同时在被相同模块的其他源文件使用时,将这个属性作为只读的。
如果创建一个TrackedString实例并且数次修改它的字符串内容,可以发现numberOfEdits属性被更新为了修改的次数:
var stringToEdit = TrackedString() stringToEdit.value = "This string will be tracked." stringToEdit.value += " This edit will increment numberOfEdits." stringToEdit.value += " So will this one." println("The number of edits is \(stringToEdit.numberOfEdits)") // prints "The number of edits is 3"
尽管你可以从其他源文件中查看到numberOfEdits属性的当前值,但你不能从其他源文件中修改这个属性。这些限制保护了TrackedString中的编辑追踪功能的实现细节,同时还提供了方便的访问这个功能的结果的方式。
你可以根据需要自行设置getter和setter的访问级别。下面的例子展示了另外一个版本的TrackedString结构体,用public定义了其访问 级别。这个结构体的成员(包括numberOfEdits属性)默认拥有internal的访问级别。你可以让结构体的numberOfEdits属性是public 的,它的setter是private的,只需要将public和private(set)组合在一起:
public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits++ } } public init() {} }
构造方法
自定义的构造方法可以被分配一个比它要构造的类型访问级别低或者相当的访问级别(译者:自定义的构造方法的访问级别不能比其类型的访问级别高)。唯一的例外就是必要构造方法(在必要构造方法(required initializers )中定义)。一个必要构造方法必须和它所在的类有相同的访问级别。
如同函数和方法参数,构造方法的参数的类型不能比构造方法自身的访问级别更低。
默认构造方法
如同默认构造方法(Default Initializers)中描述的一样,Swift自动为任意的结构体或者基本类(类要提供所有属性的默认值而且不能在任何构造方法内提供默认值)提供了不带任何参数的默认构造方法。
一个默认构造方法和它要构造的类型有相同的访问级别,除非要构造的类型是public的。对于定义为public的类型,默认的构造方法是internal。如果你需要一个共有类型被其他模块调用一个无参数的构造方法创建,你需要在类型定义中将这个无参数构造方法明确的声明为public。
结构体类型的默认成员构造方法
如果结构体属性中有任意一个是private的,那么结构体类型的默认成员构造方法也是private的。否则,将会是internal的。
和上面说的默认构造方法一样,如果想被别的模块通过调用成员构造方法创建,那么需要在类型定义中将那构造方法设置为public。
协议
如果想给一个协议类型分配一个明确的访问级别,在定义它的时候就可以做了。这样确保你创建这样的协议:在特定的访问上下文环境中才能被实现(这样确保你的协议在特定的访问上下文环境中才能被使用)。
协议中的每项要求的访问等级自动和协议的访问等级一致。你不能是设置协议不支持的访问级别给协议的要求。这样就确保了协议的所有的要去对于协议的实现类型是可见的。
NOTE
如果定义了一个共有的协议,协议的要求就需要是共有的。这点和其他类型不同,一个共有类型的定义暗示其成员是internal的。
协议继承
如果你定义了一个继承一个现有协议的新协议,新协议和它继承的协议具有相同的访问级别。例如,不能让一个public协议继承internal协议。
协议一致(protocol conformance)
一个类型可以用比自身访问级别低的方式实现一个协议。例如,你可以定义一个公有类型给其他模块使用,但是这个类型可以遵循一个internal协议,只能在内部协议被定义的模块使用。
一个类型遵循一个特定协议的访问级别是类型访问级别和协议访问级别两者最低的。如果一个类型是public的,但是它遵循的协议是internal的,那么类型对协议的协议一致是internal的。
当你写或者扩展一个类型来实现一个协议,你必须确保类型对协议的每个要求的实现至少和类型的协议一致访问级别相同。比如,如果一个公有类型的遵循了一个内部协议,那么类型对每个协议要求的实现至少要是“internal”的。
NOTE
在Swfit中,和OC中一样,协议一致是全局的——在同一程序中不可能一个类型用两种不同的方式遵循同一个协议。
扩展
可以在访问允许范围内对一个类、结构体或者枚举进行扩展。扩展时新添加的成员具有一个默认访问级别,这个访问级别就是被扩展类型中定义成员的默认访问级别。举个例子,如果扩展了一个公有类型,任何新添加的类型成员就默认具有内部访问级别。
另外,可以给出扩展标记一个明确的访问级别(比如,private extesion)来给扩展中的所有成员设置一个新的默认访问级别。这个新的访问级别仍然会被单个的类型成员重写。
用扩展添加协议一致
当需要用扩展来实现一个协议时,你不能给扩展指定一个明确的访问级别。反而,在扩展中,协议自身的访问级别被用来给每个协议要求的 实现者提供默认的访问级别。
泛型
一个泛型类型或者泛型方法的访问级别要在 其自身的访问级别与其类型参数访问级别中取最低的。
类型别名
在访问控制方面,定义的任何类型别名都被当作单独类型处理。一个类型别名的访问级别等于或低于它所替代类型的访问级别。举例说明,一个私有类型的别名可以替代一个私有的类型、内部的类型或者公有类型,但是一个公有类型的别名不能替代内部的或者私有的类型。
NOTE 这个规则同样适用于联合参数中的类型别名。