Swift文档Chapter 25 访问控制


访问控制可以限定其它源文件或模块中的代码对你的代码的访问级别。

模块和源文件

模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在Swift中,一个模块可以使用import关键字导入另外一个模块。
源文件就是Swift中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。

访问级别

Swift提供了5种访问级别:

  • OpenPublic:让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。一般用来指定外部接口
  • Internal:实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。一般指定只在模块内使用的接口。
  • File-private:实体只能在其定义的文件内部访问。功能的部分细节只需要在文件内使用。
  • Private:实体只能在其定义的作用域,以及同一文件内的extension访问。功能的部分细节只需要在当前作用域内使用。

Open只能作用于类和类的成员,它和Public的区别如下:

  • Public或者其它更严访问级别的类,只能在其定义的模块内部被继承。
  • Public或者其它更严访问级别的类成员,只能在其定义的模块内部的子类中重写。
  • Open的类,可以在其定义的模块中被继承,也可以在引用它的模块中被继承。
  • Open的类成员,可以在其定义的模块中子类中重写,也可以在引用它的模块中的子类重写。

访问级别基本原则

基本原则:实体不能定义在具有更低访问级别(更严格)的实体中。

默认访问级别

如果你没有为代码中的实体显式指定访问级别,那么它们默认为internal级别。

单target应用程序的访问级别

当你编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别internal即可。

框架的访问级别

开发框架时,就需要把一些对外的接口定义为Open或Public,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的API。

单元测试target的访问级别

当你的应用程序包含单元测试target时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有open或public级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用@testable特性,然后在允许测试的编译设置(Build Options -> Enable Testability)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。

访问控制语法

通过修饰符openpublicinternalfileprivateprivate来声明实体的访问级别。除非专门指定,否则实体默认的访问级别为internal

自定义类型

如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为private或者fileprivate级别,那么该类型的所有成员的默认访问级别也会变成private或者fileprivate级别。如果你将类型指定为internalpublic(或者不明确指定访问级别,而使用默认的internal),那么该类型的所有成员的默认访问级别将是internal

元组类型

元组的访问级别将由元组中访问级别最严格的类型来决定。

函数类型

函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。

枚举类型

枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。

原始值和关联值

枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。

嵌套类型

如果在 private 的类型中定义嵌套类型,那么该嵌套类型就自动拥有 private 访问级别。如果在 public 或者 internal 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 internal 访问级别。如果想让嵌套类型拥有 public 访问级别,那么需要明确指定该嵌套类型的访问级别。

子类

子类的访问级别不得高于父类的访问级别。我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内。

常量、变量、属性、下标

常量、变量、属性不能拥有比它们的类型更高的访问级别。

Getter 和 Setter

常量、变量、属性、下标的 Getters 和 Setters 的访问级别和它们所属类型的访问级别相同。Setter 的访问级别可以低于对应的 Getter 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 var 或 subscript 关键字之前,你可以通过 fileprivate(set),private(set) 或 internal(set) 为它们的写入权限指定更低的访问级别。

构造器

自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是必要构造器,它的访问级别必须和所属类型的访问级别相同。

默认构造器

默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 public。如果一个类型被指定为 public 级别,那么默认构造器的访问级别将为 internal。如果你希望一个public级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个public访问级别的无参数构造器。

结构体默认的成员逐一构造器

如果结构体中任意存储型属性的访问级别为private,那么该结构体默认的成员逐一构造器的访问级别就是private。否则,这种构造器的访问级别依然是internal

协议

如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意遵循者都将可用。

协议继承

如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同

协议遵循

一个类型可以遵循比它级别更低的协议。遵循协议时的上下文级别是类型和协议中级别最小的那个。当你编写或扩展一个类型让它遵循一个协议时,你必须确保该类型对协议的每一个要求的实现,至少与遵循协议的上下文级别一致。

Extension

?Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。
你可以明确指定 extension 的访问级别,从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。

Extension 的私有成员

扩展同一文件内的类,结构体或者枚举,extension 里的代码会表现得跟声明在原类型里的一模一样。

  • 在类型的声明里声明一个私有成员,在同一文件的 extension 里访问。
  • 在 extension 里声明一个私有成员,在同一文件的另一个 extension 里访问。
  • 在 extension 里声明一个私有成员,在同一文件的类型声明里访问。

泛型

泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。

类型别名

你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。