访问控制
- 访问控制可以限制其它源文件或模块对你的代码的访问。这个特性让你能够隐藏代码的实现细节,并指定一个接口来让别人访问和使用你的代码。
- 你可以给单个类型(类、结构体和枚举)设置特定的访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议可以被限制在特定的上下文中,全局常量、变量和函数也可以如此。
- Swift 不仅提供了多种访问级别,还为典型场景提供了默认的访问级别,这样就减少了我们需要显式指定访问级别的情况。实际上,如果你在开发一个单 target 的应用程序,可能完全不需要显式指定访问级别。
模块、源文件和包
- 模块是代码分发的独立单元,例如将一个框架或应用程序作为一个整体构建和发布,并且可以通过 Swift 的 import 关键字被其他模块导入。
- 在 Swift 中,Xcode 的每个构建目标(例如应用程序或框架)都被视为一个独立的模块。如果你将应用程序中的部分代码打包成一个独立的框架——以便封装这些代码并在多个应用程序中重用,那么当这个框架被导入到某个应用程序或其他框架中使用时,你在框架中定义的所有内容都将属于这个独立的模块。
- 源文件是模块中的一个 Swift 源代码文件(实际上是应用程序或框架中的一个文件)。虽然通常会将不同的类型分别定义在不同的源文件中,但同一个源文件也可以包含多个类型、函数等的定义。
- 包是一组模块的集合,这些模块作为一个整体进行开发。选择哪些模块来构成一个包,是在我们所使用的构建系统中配置的,而不是在 Swift 源代码中。
访问级别
- open 和 public 允许实体被同一模块内的任意源文件使用,也可以在导入该模块的其他模块的源文件内使用。通常使用 open 或 public 访问级别来指定框架的公共接口。
- package 允许实体被同一模块内的任意源文件使用,但不能在包外的源文件内使用。通常在包含多个模块的应用或框架中使用 package。
- internal 允许实体被同一模块内的任意源文件使用,但不能在模块外的源文件中使用。通常在定义应用或框架的内部结构体时使用 internal。
- fileprivate 将对实体的使用限制在定义它的源文件内。当某个功能的实现细节只需要在当前文件中使用时,可以使用 fileprivate 来隐藏这些实现细节。
- private 将对实体的使用限制在其声明的作用域内,以及同一文件中该声明的扩展(extension)内。当某个功能的实现细节只在单个声明内部使用时,可以使用 private 来隐藏这些实现细节。
- open 是最高(限制最少)的访问级别,而 private 是最低(限制最多)的访问级别。
- open 仅适用于类及类的成员,它与 public 的不同之处在于 open 允许模块外的代码进行继承和重写
- 将类显式指定为 open 表明你已考虑到其他模块的代码将该类用作父类的影响,并为此相应地设计了类的代码。
- 在代码中,所有实体(除了一些本章稍后会提到的特例)如果没有显式指定访问级别,那么默认的访问级别是 internal。因此,在多数情况下你不需要在代码中显式指定访问级别。
指导原则
- Swift 的访问级别遵循一个指导原则:实体的定义都不能依赖于访问级别更低(更严格)的其他实体。
- 一个 public 的变量,其类型的访问级别不能是 internal,fileprivate 或 private,因为在 public 变量被使用的地方,这些类型可能无法访问。
- 函数的访问级别不能高于它的参数类型和返回类型的访问级别,因为函数可能会在这些类型不可用的情况下被调用。
单 target 应用程序的访问级别
- 当你编写一个简单的单 target 应用程序时,这些代码通常都是只供自己使用,而不需要在应用模块之外使用。
- 因为默认的 internal 访问级别已经满足了这个需求,所以无需额外指定访问级别。
- 你也可以将某些代码的访问级别指定为 fileprivate 或 private,以便在模块内隐藏这部分代码的实现细节。
框架的访问级别
- 当你开发框架时,应将框架的对外接口指定为 open 或 public,以便其他模块(如导入该框架的应用)可以查看和访问这些接口。这个对外接口就是框架的应用程序接口(application programming interface,即 API)。
- 框架的内部实现细节仍然可以使用默认的访问级别 internal,当你需要对框架内部其它部分隐藏细节时可以使用 private 或 fileprivate。
单元测试Target的访问级别
- 当你编写包含单元测试 target 的应用程序时,需要将应用程序中的代码暴露给该模块以便进行测试。
- 如果你在导入产品模块时使用了 @testable 属性,并且在编译时启用了测试选项,那么单元测试 target 就可以访问所有 internal 实体。
访问控制语法
在实体声明的前面添加修饰符 open、public、internal、fileprivate 或 private 来定义该实体的访问级别。
open class SomeOpenClass {}
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
open var someOpenVariable = 0
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
class SomeInternalClass {} // 隐式指定为 internal
let someInternalConstant = 0 // 隐式指定为 internal
自定义类型
- 如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可
- 一个类型的访问级别也会影响该类型的成员(其属性、方法、构造器和下标)的默认访问级别。
- 如果你将一个类型的访问级别定义为 private 或 fileprivate,那么该类型的成员的默认访问级别也会是 private 或 fileprivate。
- 如果你将一个类型的访问级别定义为 internal 或 public(或者使用 internal 的默认访问级别,而不显式指定访问级别),那么该类型的成员的默认访问级别将是 internal。
- public 类型的成员默认具有 internal 访问级别,而不是 public。如果你想让某个成员是 public,必须显式地将其指定为 public。这样可以确保公共 API 都是你经过选择才发布的,避免错误地将内部使用的接口公开。
public class SomePublicClass { // 显式指定为 public 类
public var somePublicProperty = 0 // 显式指定为 public 类成员
var someInternalProperty = 0 // 隐式指定为 internal 类成员
fileprivate func someFilePrivateMethod() {} // 显式指定为 fileprivate 类成员
private func somePrivateMethod() {} // 显式指定为 private 类成员
}
class SomeInternalClass { // 隐式指定为 internal 类
var someInternalProperty = 0 // 隐式指定为 internal 类成员
fileprivate func someFilePrivateMethod() {} // 显式指定为 fileprivate 类成员
private func somePrivateMethod() {} // 显式指定为 private 类成员
}
fileprivate class SomeFilePrivateClass { // 显式指定为 fileprivate 类
func someFilePrivateMethod() {} // 隐式指定为 fileprivate 类成员
private func somePrivateMethod() {} // 显式指定为 private 类成员
}
private class SomePrivateClass { // 显式指定为 private 类
func somePrivateMethod() {} // 隐式指定为 private 类成员
}
元组类型
- 元组类型的访问级别是由元组中访问级别最严格的类型决定的。
- 元组类型不像类、结构体、枚举和函数那样有单独的定义。元组类型的访问级别是根据构成该元组类型的各个类型的访问级别自动确定的,不能显式指定。
函数类型
- 函数类型的访问级别是根据函数的参数类型和返回类型中最严格的访问级别计算得出的。
- 如果函数计算出的访问级别与上下文默认值不匹配,则必须在函数定义中显式指定访问级别。
// 将 someFunction() 函数指定为 public 或 internal,或者使用默认的 internal 访问级别都是非法的,因为函数的 public 或 internal 使用者可能无法访问函数返回类型中的 private 类。
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
}
枚举类型
- 枚举成员的访问级别和其所属的枚举类型相同。你不能为单个枚举成员指定不同的访问级别。
- 枚举定义中的原始值或关联值的类型,其访问级别至少不能低于该枚举的访问级别。例如,你不能在访问级别为 internal 的枚举中使用 private 类型作为原始值类型。
// CompassPoint 枚举被显式指定为 public 访问级别。因此,枚举成员 north、south、east 和 west 也具有 public 访问级别:
public enum CompassPoint {
case north
case south
case east
case west
}
嵌套类型
- 嵌套类型的访问级别和包含它的类型的访问级别相同,除非包含它的类型是 public。
- 定义在 public 类型中的嵌套类型,其访问级别默认是 internal。
- 如果你想让这个嵌套类型拥有 public 访问级别,那么必须显式将其声明为 public。
子类
- 你可以继承同一模块中的所有有访问权限的类,也可以继承不同模块中被 open 修饰的类。
- 子类的访问级别不得高于父类的访问级别。例如,你不能写一个 public 的子类来继承 internal 的父类。
- 对于同一模块中定义的类,你可以重写在上下文中可访问的任意类成员(方法、属性、构造器或下标)。
- 对于在其他模块中定义的类,你可以重写访问级别为 open 的任意类成员。
- 通过重写可以给子类的成员提供更高的访问级别。
- 下面的例子中,类 A 是一个 public 类,它有一个 fileprivate 的方法 someMethod()。类 B 是 A 的子类,其访问级别降低为 internal。但是,类 B 将 someMethod() 的访问级别重写为 internal,其访问级别高于原来的访问级别:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
- 即使子类成员的访问级别高于父类成员,只要调用父类成员的操作发生在允许的访问级别上下文中(例如,在同一源文件中调用父类 fileprivate 成员,在同一模块内调用父类 internal 成员),那么子类成员调用访问权限较低的父类成员也是合法的:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
// 因为父类 A 和子类 B 定义在同一个源文件中,所以子类 B 可以在重写的 someMethod() 方法中调用 super.someMethod()。
override internal func someMethod() {
super.someMethod()
}
}
常量、变量、属性、下标
常量、变量或属性的访问级别不能高于其类型的访问级别。
例如,如果一个属性的类型的访问级别是 private,那么不能将这个属性的访问级别指定为 public。
同样,下标的访问级别不能高于其索引类型或返回类型的访问级别。如果常量、变量、属性或下标的类型的访问级别是 private,那么也必须将它们的访问级别指定为 private:
private var privateInstance = SomePrivateClass()
Getters和Setters
- 常量、变量、属性和下标的 getter 和 setter 会自动获得与它们所属的常量、变量、属性或下标相同的访问级别。
- 你可以为 setter 指定一个比对应 getter 更低的访问级别,以限制该变量、属性或下标的读写范围。
- 你可以通过在 var 或 subscript 关键字之前写上 fileprivate(set)、private(set)、internal(set) 或 package(set) 来指定较低的访问级别。
- 这个规则同时适用于存储属性和计算属性。即使你没有为存储属性显式编写 getter 和 setter,Swift 仍会为你合成一个隐式的 getter 和 setter,用于访问该属性的存储内容。
- 无论是隐式合成的 setter,还是像计算属性中显式编写的 setter,使用 fileprivate(set)、private(set)、internal(set) 和 package(set) 都可以改变 setter 的访问级别。
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
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”
- 你可以在必要时为 getter 和 setter 分别指定显式的访问级别。
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
构造器
- 自定义构造器的访问级别可以低于或等于它所初始化的类型。
- 必要构造器必须具有与其所属类相同的访问级别。
- 与函数和方法的参数一样,构造器的参数类型的访问级别不能比构造器自身的访问级别更严格。
默认构造器
- 默认构造器的访问级别与它所初始化的类型相同,除非该类型被定义为 public。
- 对于 public 类型,默认构造器的访问级别将为 internal。
- 如果你想让 public 类型在另一个模块中可以通过无参数构造器进行初始化,则必须在类型定义中显式提供一个 public 访问级别的无参数构造器。
结构体默认的成员逐一构造器
- 对于结构体类型,如果结构体中的任何一个存储属性是 private,则默认的成员逐一构造器的为 private。
- 同样,如果任何存储属性是 fileprivate,则默认的成员逐一构造器为 fileprivate。
- 默认的成员逐一构造器为 internal。
- 如果你想让 public 结构体类型在其他模块中可以通过成员逐一构造器进行初始化,则必须在类型定义中显式提供一个 public 的成员逐一构造器。
协议
- 如果你想为协议类型显式指定访问级别,需要在定义协议时进行指定。这将限制该协议只能在特定的访问级别范围内被遵循。
- 协议定义中的每个要求都必须具有和该协议相同的访问级别。
- 如果你定义了一个 public 协议,那么在实现该协议时,协议的所有要求也需要具有 public 访问级别。
协议继承
- 如果你定义了一个继承自其他协议的新协议,那么新协议的访问级别最高也只能与其继承的协议相同。
- 例如,你不能定义一个继承自 internal 协议的 public 协议。
协议遵循
- 一个类型可以遵循比其自身访问级别更低的协议。
例如,你可以定义一个 public 类型,使其可以在其他模块中使用,但该类型对 internal 协议的遵循只能在定义该 internal 协议的模块中使用。 - 遵循协议时的上下文访问级别是类型和协议中访问级别最低的那个。
例如,如果一个类型是 public 的,但它遵循 internal 协议,那么这个类型对该协议遵循的上下文访问级别也是 internal 的。 - 当你编写或扩展一个类型让它遵循一个协议时,你必须确保该类型对协议每一个要求的实现至少与协议的访问级别一致。
例如,如果一个 public 类型遵循一个 internal 协议,那么该类型对协议每一个要求的实现必须至少是 internal。 - Swift 和 Objective-C 一样,协议遵循是全局的。在同一程序中,一个类型不可能用两种不同的方式遵循同一个协议。
扩展
- 可以在访问级别允许的情况下对类、结构体或枚举进行扩展。
- 在扩展中添加的类型成员具有与原始类型中声明的类型成员相同的默认访问级别。
如果你扩展的是 public 或 internal 类型,那么任何新增的类型成员默认的访问级别是 internal。
如果你扩展的是 fileprivate 类型,那么新增的类型成员默认的访问级别是 fileprivate。
如果你扩展的是 private 类型,那么新增的类型成员默认的访问级别是 private。 - 你可以使用显式的访问级别修饰符(例如 private)标记一个扩展,从而为扩展内定义的所有成员指定一个新的默认访问级别。
在此扩展内,这个新的默认级别仍然可以被单个类型成员显式指定的访问级别所覆盖。 - 如果你使用扩展来遵循协议的话,就不能为扩展提供显式的访问级别修饰符。
在这种情况下,协议自身的访问级别将被用作扩展中每个协议要求的实现的默认访问级别。
扩展的私有成员
- 扩展同一文件内的类,结构体或者枚举,扩展里的代码会表现得跟声明在原始类型里的一模一样。
这意味着你可以使用扩展来组织你的代码,无论你的类型是否包含 private 成员。
protocol SomeProtocol {
func doSomething()
}
struct SomeStruct {
private var privateVariable = 12
}
extension SomeStruct: SomeProtocol {
func doSomething() {
print(privateVariable)
}
}
泛型
泛型类型或泛型函数的访问级别取决于它本身的访问级别和其类型参数的类型约束的访问级别,最终由这些访问级别中的最低者决定。
类型别名
- 在访问控制层面,你定义的任何类型别名都被视为独立的类型。
- 类型别名的访问级别不可以高于其表示的类型的访问级别。
例如,一个 private 类型别名可以作为 private、fileprivate、internal、public 或 open 类型的别名,
但一个 public 类型别名不能作为 internal、fileprivate 或 private 类型的别名。