[Swift基础语法入门(强推)]Swift3 中的 Five Access Control Level

144
作者 NinthDay
2016.09.19 23:19* 字数 2037

Swift 中包含五大 Access Control Levelopenpublicinternalfileprivateprivate(highest access level -> lowest access level,换句话说 least restrictive -> most restrictive 下文有详细解释)

  • open 和 public 定义的 entity 允许被所有作用域(包括当前模块内文件或是其他模块文件)访问;
  • internal 作用范围仅限在 entity 所定义的模块内部,其他模块文件无法访问。ps:默认 Access Control Level 为 Internal;
  • fileprivate 作用范围为当前文件,因此一个文件内定义多个类,某个类标记为 fileprivate 之后,当前模块内的其他文件无法访问这个类,而当前文件内定义的其他类可以访问;
  • private 只允许当前作用域访问。

open 只能应用于类和类成员,与 public 的不同之处在于:

  • public 以及其他 more restrictive 访问级别只能在定义的模块内被继承;
  • public 以及其他 more restrictive 访问级别只能在定义的模块内被重写;
  • open 则既可以在定义的模块或是其他模块内被继承或重写。

重要指导思想

一个优先级较高(约束力很弱,谁都能访问)的实体无法在一个优先级较低(约束力条件很强,不是谁都能访问)的实体内被定义。原文:No entity can be defined in terms of another entity that has a lower (more restrictive) access level.

  • 标记为 public 的变量 x 定义在一个 internalfileprivateprivate 类型 A 内部是不恰当的,分析如下:类型 A 既然被标记为 internal,只允许被当前模块访问,那么即使内部定义一个 public 标记的属性,也无法其他模块访问到,因此毫无意义;
  • 函数被标记的访问优先级不能高于它的传参和返回值。分析如下:首先优先级高意味着约束力较弱,容易被访问,那么定义一个 public 函数 funcA 在模块 A 中,传参和返回值类型使用默认internal(只允许对当前模块开放)。那么问题来了:模块 B 可以访问到函数funcA ,传入模块 B 中的参数我首先要实例化才能传入,实例化操作又必须允许我访问那个参数类型。但由于标记了 internal,所以其他模块中显然不允许;返回值类型同样是这个道理。

自定义类型中的 Access Control Level

如果自定义的类型被标记为 fileprivateprivate ,那么该类型中的成员默认为 fileprivateprivate ;如果自定义的类型被标记为 publicinternal,那么类型成员默认为internalNote:并不是 Public,当然你可以明确标记为 public)。

Note:例子中类标记的 Access Control Level 优先级一定 >= 类内部成员的优先级! 再次强调优先级越高约束力越弱,优先级越低约束力越高。优先级从高到低排列:open public internal(默认) fileprivate private

public class SomePublicClass {                  // 明确为 public class
    public var somePublicProperty = 0            // 明确为 public class member
    var someInternalProperty = 0                 // => 隐性为 internal class member
    fileprivate func someFilePrivateMethod() {}  // 明确为 file-private class member
    private func somePrivateMethod() {}          // 明确为 private class member
}

class SomeInternalClass {                       // => 隐性为 internal class
    var someInternalProperty = 0                 // => 隐性为 internal class member
    fileprivate func someFilePrivateMethod() {}  // 明确为 file-private class member
    private func somePrivateMethod() {}          // 明确为 private class member
}

fileprivate class SomeFilePrivateClass {        // 明确为 file-private class
    func someFilePrivateMethod() {}              // => 隐性为 file-private class member
    private func somePrivateMethod() {}          // 明确为 private class member
}

private class SomePrivateClass {                // 明确为 private class
    func somePrivateMethod() {}                  // => 隐性为 private class member
}

Tuple 类型

元组类型的访问级别是取它包含的所有成员类型的最低优先级(The most restrictive,约束力最强)。比如,组合一个 private 类型和一个 internal 类型成为一个元组,那么当前元组类型访问优先级为 private

Tuple 类型无法被单独明确定义(若你持有怀疑,不妨一试),这与类、结构体、枚举、函数略有不同。它会根据要组合的多个类型访问级别来自动限制元组的 Access Control Level。

函数

函数通常有传参返回值,和 Tuple 类型类似,函数自身的访问级别是取传参和返回值类型两者中的最低优先级。如果函数根据上下文(传参和返回值)得到的访问级别和默认不匹配(不一定是internal,若有疑惑,请见上面例子),那么就需要你明确标记访问级别。

func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

someFunction 并未明确 Access Control Level,这里上下文得到默认访问级别为 internal。但是注意返回值 (SomeInternalClass, SomePrivateClass) 元组,根据前一章得到的知识,我们得到元组的访问级别为 private,在根据本章一开始说的,函数的访问级别不难得出: private。和默认的 internal相悖,因此我们需要明确标记访问级别了。

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

枚举类型

枚举中的各个 case 访问级别和枚举的 Access Control Level 保持一致。

Raw Values and Associated Values

The types used for any raw values or associated values in an enumeration definition must have an access level at least as high as the enumeration’s access level. You cannot use a private type as the raw-value type of an enumeration with an internal access level, for example.

内嵌类型

  • 内嵌到一个 private 类型,默认访问级别为 private
  • 内嵌到一个 fileprivate 类型,默认访问级别为 fileprivate
  • 内嵌到一个 publicinternal 类型,默认访问级别为 internal,当然如果你想对其他模块开放,明确标记为 public即可

继承

subclass 的 Access Control Level 优先级不能高于 superclass 的优先级,比如你继承了一个标记为internal的父类,那么子类无法被标记为 public

override 重写一个父类的方法或属性,可以提高访问级别,前提是: 当前上下文中,重写属性必须可以访问!官方提供的例子有误。见例:

public class A {
    private func someMethod() {} //=> 私有 官方提供例子标记为 private 我认为改成 fileprivate 
}

internal class B: A {
    override internal func someMethod() {} // => 变成模块可见 当然也不能是public 因为外部限制为internal了
}

原文描述如下:In addition, you can override any class member (method, property, initializer, or subscript) that is visible in a certain access context.

这里明确说了需要重写的类成员必须在当前访问上下文中可见。但是官方提供的例子是 private ,对 B 根本不可见,所以我认为可能是笔误,应该写成 fileprivate

重写方法中允许调用父类方法,前提是当前访问上下文必须对其可见:

public class A {
    fileprivate func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}

常量,变量,属性和下标

常量,变量或属性优先级不能设置的比声明类型的优先级高,举例:

internal var privateInstance = SomePrivateClass()

设置了 internal 说明这个 privateInstance 允许向当前模块的其他源文件开放,读没有问题,但是写操作是无法实现的,你想实例化一个新的SomePrivateClass类,但是由于私有限制,你无法在其他文件使用这个类。

Getters & Setters

Getter 和 Setter 方法自动默认和声明属性的访问优先级一致,但是允许 Setter 方法的Access Control Level 较 Getter 方法低一些,即访问限制变大,可能原来外部能访问到的,现在只能内部访问了。一般我们会用 fileprivate(set), private(set),和 internal(set) 来明确属性的setter 访问等级较默认低一些。举例

struct TrackedString {
    private(set) var numberOfEdits = 0 
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}

numberOfEdits 变量的默认访问级别为 internal ,现在设置为 private(set)后,只有在 TrackedString 结构体内部能够 Setter 写,外部只能Getter 读。

类型别名

自定义的类型别名的 Access Control Level 允许小于等于(<=) 它绑定绑定的访问优先级。例如:

// 源文件main.swift 中定义如下:
fileprivate typealias myType = Int64 

private typealias mymyType = myType // ✅ 
typealias mymyType = myType // ❌

分析如下:类型别名 myType 访问域仅限当前的 main.swift文件,那么再为它设置一个别名 mymyType 不管如何最多只能在当前文件下被使用,所以访问级别应该 <= fileprivate ,要么是 fileprivate 或是 private。 而标记了❌的代码行错误原因是默认是 internal 访问级别,显然和myType 初衷相悖。

更多

更多请见官方文档:The Swift Programming Language - Access Control
卓同学 Swift3 必看系列中的:Swift 3必看:新的访问控制fileprivate和open

欢迎关注我的微博:@pmst

Swift 基础语法入门