访问控制可以限定其他源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
如果只是开发一个单一 target 的应用程序,完全可以不用显式申明代码的访问级别。
模块和源文件(Modules and Source Files)
Swift 中的访问控制模型基于模块和源文件这两个概念。
模块指的是独立的代码单元,框架framework或应用程序会作为一个独立的模块来构建和发布。
在 Swift 中,一个模块可以使用import关键字导入另外一个模块。
在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。
访问级别(Access Levels)
public:可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,可以将其设置为 public 级别。
internal:可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况下,某个接口只在应用程序或框架内部使用时,可以将其设置为 internal级别。
private:限制实体只能在所在的源文件内部使用。使用 private
级别可以隐藏某些功能的实现细节。public为最高(限制最少)访问级别,private为最低(限制最多)访问级别。
Swift 中的 private访问级别不同于其他语言,它的范围限于源文件,而不是声明范围内。这就意味着,一个类型可以访问其所在源文件中的所有 private实体,但是如果它的扩展定义在其他源文件中,那么它的扩展就不能访问它在这个源文件中定义的 private实体。
一个类一个源文件是一个好习惯
访问级别基本原则(Guiding Principle of Access Levels)
- Swift 中的访问级别遵循一个基本原则:不可以在某个实体中定义访问级别更高的实体。
外面的一定要比里面的高
一个 public访问级别的变量,其类型的访问级别不能是 internal或 private。因为无法保证变量的类型在使用变量的地方也具有访问权限。
函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为如果函数定义为 public而参数类型或者返回类型定义为 internal或 private,就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以。
默认访问级别(Default Access Levels)
- 默认为 internal级别,在大多数情况下,不需要显式指定实体的访问级别。
单 target 应用程序的访问级别(Access Levels for Single-Target Apps)
默认的访问级别 internal,不需要明确设置访问级别。
可以使用 private级别,用于隐藏一些功能的实现细节。
框架的访问级别(Access Levels for Frameworks)
对外的接口定义为 public级别,以便使用者导入该框架后可以正常使用其功能。
被定义为 public的接口,就是这个框架的 API。
框架依然会使用默认的 internal级别,也可以指定为 private级别。
单元测试 target 的访问级别(Access Levels for Unit Test Targets)
- 在导入应用程序模块的语句前使用 @testable特性,然后在允许测试的编译设置(Build Options -> Enable Testability)下编译这个应用程序模块,单元测试 target 就可以访问应用程序模块中所有 internal级别的实体。
访问控制语法(Access Control Syntax)
通过修饰符 public、internal、private来声明实体的访问级别。
除非专门指定,否则实体默认的访问级别为 internal。
自定义类型(Custom Types)
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。
一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。
一个 public类型的所有成员的访问级别默认为 internal级别,而不是 public级别。如果想将某个成员指定为 public级别,那么你必须显式指定。
如果将类型指定为private级别,那么该类型的所有成员的默认访问级别也会变成 private。
public class SomePublicClass { // 显式的 public 类
public var somePublicProperty = 0 // 显式的 public 类成员
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}
class SomeInternalClass { // 隐式的 internal 类
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}
private class SomePrivateClass { // 显式的 private 类
var somePrivateProperty = 0 // 隐式的 private 类成员
func somePrivateMethod() {} // 隐式的 private 类成员
}
元组类型(Tuple Types)
元组的访问级别将由元组中访问级别最严格的类型来决定。
元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
函数类型(Function Types)
- 函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
}
将该函数指定为 public或 internal,或者使用默认的访问级别 internal都是错误的,因为如果把该函数当做public或 internal级别来使用的话,可能会无法访问 private级别的返回值。
枚举类型(Enumeration Types)
- 枚举成员的访问级别和该枚举类型相同,不能为枚举成员单独指定不同的访问级别。
public enum CompassPoint {
case North
case South
case East
case West
}
// 成员 North、South、East、West的访问级别同样也是 public
原始值和关联值(Raw Values and Associated Values)
- 枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,不能在一个 internal访问级别的枚举中定义 private级别的原始值类型。
嵌套类型(Nested Types)
如果在 private级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 private访问级别。
如果在 public或者internal 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 internal访问级别。
如果想让嵌套类型拥有public访问级别,那么需要明确指定该嵌套类型的访问级别。
子类(Subclassing)
子类的访问级别不得高于父类的访问级别。
可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。
可以通过重写为继承来的类成员提供更高的访问级别。通过这种方式,可以将某类中private级别的类成员重新指定为更高的访问级别,以便其他人使用
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
- 可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 private级别的成员,在同一模块内访问父类 internal级别的成员)
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
常量、变量、属性、下标(Constants, Variables, Properties, and Subscripts)
常量、变量、属性不能拥有比它们的类型更高的访问级别。
下标也不能拥有比索引类型或返回类型更高的访问级别。
如果常量、变量、属性、下标的类型是 private 级别的,那么它们必须明确指定访问级别为 private。
Getter 和 Setter(Getters and Setters)
常量、变量、属性、下标的 Getters和 Setters的访问级别和它们所属类型的访问级别相同。
Setter的访问级别可以低于对应的 Getter的访问级别,这样就可以控制变量、属性或下标的读写权限。在 var或subscript关键字之前,可以通过 private(set)或 internal(set)为它们的写入权限指定更低的访问级别。
可以在必要时为 Getter和 Setter显式指定访问级别。
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
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)")
// 打印 “The number of edits is 3”
构造器(Initializers)
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。
必要构造器(required)的访问级别必须和所属类型的访问级别相同。
构造器参数的访问级别也不能低于构造器本身的访问级别。
默认构造器(Default Initializers)
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 public。如果一个类型被指定为 public级别,那么默认构造器的访问级别将为 internal。
如果希望一个 public级别的类型也能在其他模块中使用这种无参数的默认构造器,只能自己提供一个 public访问级别的无参数构造器。
结构体默认的成员逐一构造器(Default Memberwise Initializers for Structure Types)
如果结构体中任意存储型属性的访问级别为 private,那么该结构体默认的成员逐一构造器的访问级别就是 private。否则,这种构造器的访问级别依然是 internal。
如果希望一个 public级别的结构体也能在其他模块中使用其默认的成员逐一构造器,只能自己提供一个 public访问级别的成员逐一构造器。
协议(Protocols)
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。
协议中的每一个要求都具有和该协议相同的访问级别。不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意实现者都将可用。
如果你定义了一个 public访问级别的协议,那么该协议的所有实现也会是 public访问级别。
协议继承(Protocol Inheritance)
- 如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,不能将继承自 internal协议的新协议定义为 public协议。
协议一致性(Protocol Conformance)
一个类型可以遵循比自身访问级别低的协议。例如,可以定义一个 public级别的类型,它可以在其他模块中使用,同时它也可以遵循一个 internal级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。
遵循了协议的类型的访问级别取它本身和所遵循协议两者间最低的访问级别。也就是说如果一个类型是 public级别,遵循的协议是 internal级别,那么遵循了这个协议后,该类型作为符合协议的类型时,其访问级别也是 internal。
如果遵循了协议,那么实现了协议的所有要求后,必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 public级别的类型,遵循了 internal级别的协议,那么协议的实现至少也得是 internal级别。
协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
扩展(Extensions)
扩展成员具有和原始类型成员一致的访问级别。例如,扩展了一个 public或者 internal类型,扩展中的成员具有默认的 internal访问级别,和原始类型中的成员一致 。如果扩展了一个 private类型,扩展成员则拥有默认的 private访问级别。
可以明确指定扩展的访问级别(例如,private extension),从而给该扩展中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
通过扩展添加协议一致性(Adding Protocol Conformance with an Extension)
- 如果通过扩展来采纳协议,那么就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。
泛型(Generics)
- 泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。
类型别名(Type Aliases)
定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。
类型别名的访问级别不可高于其表示的类型的访问级别。例如,private级别的类型别名可以作为 public、internal、private类型的别名,但是 public级别的类型别名只能作为 public类型的别名,不能作为 internal或 private类型的别名。
- Swift只有源文件,没有头文件,这个和Object-C完全不同。再也不用纠结变量和函数定义应该放哪个文件了。这个特性推荐
- 默认的全局访问internal,带来了很大的方便。再也不需要一个归总的header文件,也不需要pch文件,作用只是方便访问
- private的作用范围是源文件,而不是所在的类型,这个像C语言中的静态全局变量。这个也是推荐的
- 需要隐藏的内容需要手动指定private,这个思路和C相反,但是推荐。在一个target中,内部公开,方便访问是第一优先级的。隐藏需要特别指定,这个符合思维习惯,推荐。
- 写framework的时候,默认是不公开的,对外接口需要显示用public指定,这个思路也支持。
- 协议强调访问的一致性,这个特性支持。简单好用
- 元组,泛型,类型别名,只能低,不能高的思路也支持。简单好用
- 继承通过重写可以提升级别的特性,持保留态度。迁就于现有代码,可以理解。但是自己设计的新类,如果用到这种思路,需要重新思考整体设计思路。这个显然不推荐。