访问控制限制对其他源文件和模块中的代码中你其他部分的代码的访问。这个特性使你可以隐藏代码的实现细节,并指定一个首选接口,通过该接口可以访问和使用代码。
可以给独立的类型分配特殊的访问等级(类,结构体,和枚举),属性,方法,初始化器和下标也一样属于这些类型。协议可以限制为一个确定的环境,和全局常量,变量和函数一样可以。
除了提供不同的访问可控制等级,swift通过为标准场景提供默认的访问等级减少了指定明确访问控制等级的需要。的确,如果你写一个单目标的app,你可能完全不需要指定明确的访问等级。你的对他们(属性,类型,函数,等等)有不同访问权限的应用的代码的不同方面像下面章节中的“实体”一样被引用,为了方便。
模块和数据文件(Modules and Source Files)
swift的访问控制模型在模块和源文件的定义基础上。
一个模块是一个单一的代码发布的单元-一个作为一个单独的单元创建和装载的framework或者application并且可以用swift的import关键字被其他模块导入。
xcode中每一个建立目标(例如app 包或者framework)在swift中作为独立的模块对待。如果你把你的app的代码的各个方面作为一个独立的framework组织起来--可能在多个applications中封装和重使用这些代码--那么你在framework中定义的全部东西在它在一个APP中导入并使用的时候会是一个独立的模块的一部分,或者在其他framework中使用它的时候。
在模块中(实际上在一个app或者framework中的文件)一个源文件是一个独立的swift源码文件。即使在独立的源文件中定义独立的类型很正常,一个单独的源文件可以包含多个类型,函数,等等的定义。
访问等级(Access Levels)
swift为代码中的实体提供五个不同的访问等级。这些访问等级和定义实体的源文件有关,也和源文件属于的模块有关。Open访问和public访问在定义他们的模块中的任何源文件中使用,也可以在导入定义的模块的其他模块中的源文件中。当给一个framework指定公共接口的时候通常使用open或者public访问。open和public访问的不同在下面描述。
internal访问使实体可以被定义他们的模块中的任何源文件使用,但不是模块外的任何源文件。一般在定义app的或者framework的内部结构的时候使用internal访问。
File-private访问对它自己的定义的源文件的使用限制。当这些细节用在一个实体文件中的时候用file-private访问来隐藏指定的功能的片段的实现细节。
Private访问将实体的使用限制在封闭的声明中,和在相同文件中的扩展的声明。当这些细节值用在单个的声明中的时候用private访问来隐藏指定功能片段的实现细节。
open访问是最高(最少限制)的访问等级并且private访问是最低(最有限制)的访问等级。
open访问只对类和类成员使用,通过允许模块外的代码来子类化和重写来和public访问区别,像下面Subclassing中描述的。把类标记为open明确的指明了你已经考虑了从其他模块中把该类作为父类的代码的影响,相应的你已经设计了你的类的代码。
访问等级的引导原则(Guiding Principle of Access Levels)
swift最后弄得访问等级遵循全部的引导原则:实体不可以被其他有低级(限制更严格)访问等级的实例定义。
例如:一个public变量不能被定义为有internal,file-private,或者private的类型,因为类型可能不会在任何使用public变量的地方都可以得到。
函数不能有比他的参数类型和返回类型更高的访问等级,因为函数可以在周围代码不能得到它的组成类型的地方使用。
对于语言的不同层面的关于这个引导原则的特定的含义在下面详细的说明。
默认访问等级(Default Access Levels)
如果你自己没有指定明确的访问等级,你的代码中的全部的实体(有一点特别的异常,像这一章节后面描述的)有一个默认的internal的访问等级。结果就是,许多情况中你不需要在你的代码中指定一个明确的访问等级。
单目标APP的访问等级(Access Levels for Single-Target APPS)
当你写一个单目标APP的时候,APP中的代码通常是在APP中self-contained的并且不需要被APP模块外获取到。默认的internal访问等级满足这个要求。所以,你不需要指定一个自定义的访问等级。你可能,不过,像把部分代码标记为file private或者private来对APP的模块中其他的代码隐藏他们的实现细节。
frameworks的访问等级(Access Levels for Frameworks)
当你开发一个framework的时候,把对那个framework标记的public-facing接口标记为open或者public,所以他可以被其他模块查看和访问,例如一个导入这个framework的app。这个public-facing的接口是这个framework的软件编程接口(或者API)。任何你的framework的internal实现细节仍然可以使用默认的internal访问等级,或者如果你想对framework的internal代码的其他部分隐藏他们可以把它标记为private或者file private。你只有想他作为你的framework的API的一部分的时候才需要把实体标记为open或者public。
单元测试目标的访问等级(Access Levels for Unit Test Targets)
当你用一个单元测试目标写一个APP的时候,你的APP中的代码为了被测试需要对模块是可访问的。默认的,只有标记为open或者public的实体可以被其他模块访问。不过,一个测试单元目标可以访问任何internal实体,如果你把一个成果模块的导入声明用@textable属性标记并且允许测试来编译那个成果模块。
访问控制语法(Access Control Syntax)
通过在实体声明开始放置一个open,public,internal,fileprivate,或者private修饰词来为实体定义访问等级。
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}复制代码
除非指定了,默认的访问等级是internal,像在Default Access Levels中描述的。这意味着可以没有明确的访问等级修饰词来写SomeInternalClass和someInternalConstant,仍然会有一个internal访问等级:
class SomeInternalClass {} // implicitly internal
let someInternalConstant = 0 // implicitly internal复制代码
自定义类型(Custom Types)
如果你想给自定义类型指定明确的访问等级,在你定义类型的时候做这些。新的类型可以在它的访问等级允许的地方使用。例如,如果你定义一个file-private类,这个类只可以作为属性的类型,或者函数的参数,或者返回类型,在file-private类定义的源文件中。
一个类型的访问控制等级也影响类型成员的默认访问等级(它的属性,方法,初始化器,下标)。如果你定一个类型的访问等级为private或者file private,它的成员的默认访问等级就也会是private或者file private。若果你定义类型的访问等级为internal或者public(或者没有明确的指定访问等级使用默认访问interna等级),默认的类型成员的访问等级会是interna。一个public类型默认有internal成员,不是public成员。如果你想type成员为public,你必须明确的这样指定。这个要求确保了一个类型的public-facing API是你选择公布的东西,避免把类型的internal内容错误的作为public API展示出来。
public class SomePublicClass { // explicitly public class
public var somePublicProperty = 0 // explicitly public class member
var someInternalProperty = 0 // implicitly internal class member
fileprivate func someFilePrivateMethod() {} // explicitly file-private class member
private func somePrivateMethod() {} // explicitly private class member
}
class SomeInternalClass { // implicitly internal class
var someInternalProperty = 0 // implicitly internal class member
fileprivate func someFilePrivateMethod() {} // explicitly file-private class member
private func somePrivateMethod() {} // explicitly private class member
}
fileprivate class SomeFilePrivateClass { // explicitly file-private class
func someFilePrivateMethod() {} // implicitly file-private class member
private func somePrivateMethod() {} // explicitly private class member
}
private class SomePrivateClass { // explicitly private class
func somePrivateMethod() {} // implicitly private class member
}复制代码
元祖类型(Tuple Types)
元祖类型的访问等级是所有用在那个元祖中的类型中最限制访问的等级。例如,如果你用两个不同的类型组成一个元祖,一个由internal 访问和一个private访问,组合的元祖类型的访问等级是private。元祖没有类,结构体,枚举,函数的单独的定义方式。元祖类型的访问等级从组成元祖的类型自动确定,不能明确指定。
函数类型(Function Type)
函数类型的访问顶级是通过函数参数类型和返回值类型的最限制的访问类型计算的。如果函数计算的范文登记和上下文默认的不匹配那么必须在函数定义的时候明确指定访问等级。
例如下面定义了一名为someFunction()的全局函数,没有为函数自己提供一个指定的访问等级修饰词。你可能期望函数有默认的internal访问等级,但不是这种情况。实际上,someFunction()像下面这样写的话不会编译:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}复制代码
函数的返回类型是一个用定义在上面Custom Types中的自定义类组合的元祖类型。这些类中有一个定义为internal,另一个定义为private。所以,组合元祖类型的重叠访问等级是private(元祖组成类型的最低访问等级)。
因为函数的返回类型是private,必需在函数声明中用private修饰词标记函数的整体访问等级来使其有效:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}复制代码
用public和internal修饰词标记someFunction()是不合法的,或者用默认的internal设置,因为函数的public或者internal使用者可能没有对在函数返回类型中的private类的合适的访问。
枚举类型(Enumeration Types)
枚举的独立的cases自动接收和他所属的枚举一样的访问等级。你不能问单独的枚举cases指定不同的访问等级。
下面的例子中,CompassPoint枚举有一个明确的public的访问等级。枚举cases north,south,east,和west也有public的访问等级:
public enum CompassPoint {
case north
case south
case east
case west
}复制代码
Raw值和关联值(Raw Values and Associated Values)
枚举定义中的任何raw值和关联值使用的类型必需有至少和枚举的访问等级一样高的访问等级。例如,不能使用private类型作为internal访问等级的枚举的raw-value的类型。
内嵌类型(Nested Types)
内嵌类型的访问等级和包含的类型一样的访问等级,除非包含的类型是public。定义在public类型中的内嵌类型有一个自动的internal访问等级。如果你想要在public类型中的内嵌类型是公共访问的,必须要明确把内嵌类型声明为public。
子类(Subclassing)
你可以子类化任何在当前访问环境中可以访问和在作为子类在同一个模块中定义的类。你也可以子类化任何在不同的模块中定义的open的类。子类不可以有比父类更高的访问等级--例如,不能写一个internal父类的public子类。
另外,对于定义在同一个模块中的类,可以重写任何在确定的访问环境中可以看到的类成员(方法,属性,初始化器,下标)。对于在其他模块中定义的类,你可以重写任何open类成员。
重写可以让继承的类比父类版本更好理解。在下面的例子中,类A是一个有名为someMethod()的file-private方法的public类。类B是A的子类,有更低的internal访问等级。然而,类B提供了一个有internal访问等级的someMethod()方法的重写,比原始的someMethod()实现更高的等级:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}复制代码
对于自类的成员调用比子类成员低的访问等级权限父类的成员是合法的,只要调用父类的成员发生在允许的访问等级环境(也就是,在和父类一样的源文件的file-private成员调用,或者和父类在一样的模块中的internal成员的调用):
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}复制代码
因为父类A和子类B定义在一样的源文件中,对于B的someMethod()实现中调用super.someMethod()是有效的。
常量,变量,属性,和下标(Constants,Variables,Properties,和Subscripts)
常量,变量,或者属性不能比它的类型更加开放。在private类型中写一个public属性是不合法的,例如。相似的,下标不能比他的索引类型或者返回类型开放。
如果一个常量,变量,属性或者下标使用private 类型,那么常量,变量,属性,或者下标也必须标记为private:
private var privateInstance = SomePrivateClass()复制代码
Getters 和 Setters(Getters and Setters)
常量,变量,属性,和下标的Getter和Setter自动接收和一个和他们属于的常量,变量,属性,或者下标一样的访问等级。
你可以给setter一个比它对应的getter更低的访问等级,来限制变量,属性,或者下标的读写区域。通过在var或者subscript介绍词前些fileprivate(set),private(set),或者internal(set)来分配一个更低级的访问等级。用于存储属性的规则也用于计算属性。即使你没有为存储属性明确的写getter和setter,swift仍然为你合成一个隐式的getter和setter来给存储属性的背后存储提供访问。用和一个在计算属性中明确的setter一样的方式使用fileprivate(set),private(set),和internal(set)来修改这个合成的setter的访问等级。
下面的例子定义了一个名为TrackedString的结构体,追踪一个string属性被修改次数的数字:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}复制代码
TrackedString结构体定义一个存储的名为value的string属性,有一个“”初始值(一个空字符串)。结构体也定义了一个名为numberOfEdit是的存储的integer属性,用来追中value值被修改的次数的数字。这个修改的最终用一个在value属性上的didSet属性观察者来实现,每次value属性设置为一个新的值的时候增加numberOfEdits。
TrackedString结构体和value属性没有提供明确的访问等级修饰词,所以他们接受一个默认的internal访问等级。不过,numberOfEdits属性的访问等级用private(set)修饰词标记来指明属性的getter任然有默认的internal访问等级,但是属性只在TrackedString结构体中的代码是settable的。这使得TrackedString修改numberOfEdits属性是internally的,但是当在结构体定义之外使用的时候是只读的属性。
如果你创建了一个TrackedString实例并且偶尔修改它的string值,你可以看到numberOfEdits属性的值会更新来匹配修改的次数:
var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// Prints "The number of edits is 3"复制代码
初始化器(Initializers)
可以给自定义初始化器分配一个等于或者低于他们初始化的类型的访问等级。唯一的异常是对于必需的初始化器(像在Required Initializers中定义的)。一个必需的初始化器必须和它属于的类有一样的访问等级。
与函数和方法参数一样,初始化器的参数的类型不能比初始化器自己的访问等级更私有。
默认初始化器(Default Initializers)
像在Default Initializers中描述的,swift自动为任何给他的属性提供了默认值并且没有提供一个初始化器的结构体或者基础类提供一个没有参数的默认初始化器。
一个默认的初始化器由和他初始化类型一样的访问等级,除非他定义的类型是public。对于定义为public的类型,默认的初始化器认为是internal。如果你想public类型在其他模块中用无参数的初始化器初始化,必需在即在类型的定义中明确提供一个public的无参数初始化器。
结构体类型的默认成员初始化器(Default Memberwise Initializers for Structure Types)
如果任何结构体的存储属性是private的,结构体类型默认的初始化器认为是private的。同样的,如果任何结构体的存储属性是file private,初始化器时file private。否则,初始化器由internal访问等级。
像上面的默认初始化器,如果你想在其他模块使用他的时候用成员初始化器初始化public结构体类型,你自己必需在类型的定义中提供一个public成员初始化器。
协议(Protocols)
如果你想给协议类型提供一个明确的访问等级,在你定义协议的时候这样做。这使你可以创建只能在确定的访问环境中采用的协议。
在协议定义中每个要求的访问等级自动设置为和协议一样的访问等级。不能把协议要求设置为和协议支持的的访问等级不同的访问等级。这确定了在采用了协议的任何类型中全部的协议要求都是可见的。如果你定义了一个public协议,当他们被实现的时候协议的要求需要为这些要求一个public访问等级。这个表现和其他类型不同,一个public类型定义给类型的成员一个internal的访问等级。
协议继承(Protocol Inheritance)
如果你定义了一个新的协议,继承自已经存在的协议,新的协议可以有和他继承的协议一样的访问等级。例如,你不能写一个继承自internal协议的public协议。
协议遵循(Protocol Conformance)
一个类型可以遵循比类型自己低的访问等级的协议。例如,你可以定义一个可以用在其他模块中的public类型,但是类型对internal协议的遵守只可以用在internal协议的定义模块中。
类型遵循一个特定协议的上下文是类型的访问等级和协议的访问等级。例如,如果一个类型是public,但是它遵循的协议是internal,遵循那个协议的类型也是internal。
当你写或者扩展一个类型遵循一个协议的时候,必需确保对于每个协议要求的类型的实现有至少和对那个协议的类型的遵循一样的访问等级。例如,如一个public类型遵循一个internal协议,每个协议要求的类型的实现必需至少是internal。swift中,像在Objective-C,协议遵循是全局的--对于一个类型不可能用两种方式在相同的项目中遵循一个协议。
扩展(Extensions)
你可以在任何可以获取到类,结构体,或者枚举的访问环境中扩展类,结构体,或者枚举。任何加载扩展中的类型成员有和声明在被扩展的原始类型中的类型成员一样的默认访问等级。如果你扩展一个public或者internal类型,任何你增加的新的类型成员有一个默认的internal访问等级。若果你扩展一个file-private类型,你增加的任何类型有一个默认的file-private访问等级。如果你扩展一个private类型,任何你增加的新的类型成员有一个默认的private访问等级。
另外,你可以用一个明确的访问等级修饰词(例如private)标记扩展来把定义在扩展中全部的成员设置为新的默认访问等级。这个新的默认值仍然可以在扩展中为独立的类型成员重写。
如果你正在使用这个扩展来增加协议的遵循,不能为扩展提供一个明确的访问等级修饰词。而是,协议自己的访问等级用来在扩展中为每个协议要求的实现提供默认的访问等级。
扩展中的private成员(Private Members in Extensions)
和他们扩展的类,结构体,或者枚举在一个文件中的扩展表现像扩展中的代码已经在原始的类型声明中写过了。结果是,你可以:在原始声明中声明一个private成员,在同一个文件中从扩展中访问这个成员
在一个扩展中声明一个成员,在同一个文件中的另一个扩展中使用这个成员
在一个扩展中声明一个private成员,在同一个文件的原始声明中访问成员
这个表现意味着你可以用一样的方式使用扩展来组织你的代码,不管你的类型有没有private实体。例如,给一个下面简单的协议:
protocol SomeProtocol {
func doSomething()
}复制代码
可以使用扩展来增加协议遵守,像这样:
struct SomeStruct {
private var privateVariable = 12
}
extension SomeStruct: SomeProtocol {
func doSomething() {
print(privateVariable)
}
}复制代码
泛型(Generics)
泛型类型后者泛型函数的访问等级是泛型类型或者函数自身的访问等级和任何在它的类型参数上的类型约束的访问等级的最小值。
类型别名(Type Aliases)
出于访问控制的目的,任何你定义的类型别名作为不同的类型。一个类型别名可以有小于或者等于它取的别名的类型的访问等级。例如,一个private类型别名可以叫做private,file-private,internal,public,或者open类型,但是一个public类型别名不能作为一个internal,file-private,或者private类型的别名。这些规则也用于用来满足协遵循议的关联类型的类型别名。