Swift5.4 语言指南(二十八) 访问控制
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739969.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
访问控制限制从其他源文件和模块中的代码访问部分代码。使用此功能,您可以隐藏代码的实现细节,并指定可以访问和使用该代码的首选接口。
您可以为单个类型(类,结构和枚举)以及属于这些类型的属性,方法,初始化程序和下标分配特定的访问级别。协议可以限制在特定的上下文中,全局常量,变量和函数也可以。
除了提供各种级别的访问控制之外,Swift通过为典型方案提供默认访问级别来减少指定显式访问控制级别的需要。确实,如果您正在编写一个单一目标的应用程序,则可能根本不需要指定显式的访问控制级别。
笔记
为了简洁起见,可以在其上应用访问控制的代码的各个方面(属性,类型,函数等)在以下各节中称为“实体”。
模块和源文件
Swift的访问控制模型基于模块和源文件的概念。
甲模块是代码分布框架或应用程序的内置和运输为单个单元,并可以通过与Swift的另一个模块引入的单个单元import
关键字。
Xcode中的每个构建目标(例如应用程序捆绑包或框架)在Swift中都被视为一个单独的模块。如果您将应用程序代码的各个方面归为一个独立的框架(可能是在多个应用程序之间封装和重用该代码),那么当在应用程序中导入和使用该框架时,您在该框架中定义的所有内容都将成为单独模块的一部分,或在其他框架中使用它时。
甲源文件是一个模块内的单个夫特源代码文件(实际上,一个应用程序或框架内的一个单独的文件)。尽管通常在单独的源文件中定义单个类型,但是单个源文件可以包含多个类型,函数等的定义。
访问级别
Swift为您代码中的实体提供了五种不同的访问级别。这些访问级别与定义实体的源文件有关,也与源文件所属的模块有关。
- 开放访问和公共访问使实体可以在其定义模块的任何源文件中使用,也可以在导入定义模块的另一个模块的源文件中使用。指定框架的公共接口时,通常使用开放访问或公共访问。开放和公共访问之间的区别如下所述。
- 内部访问使实体可以在其定义模块的任何源文件中使用,但不能在该模块外部的任何源文件中使用。在定义应用程序或框架的内部结构时,通常使用内部访问。
- 文件专用访问将实体的使用限制为自己定义的源文件。当在整个文件中使用特定功能的实现细节时,使用文件专用访问权限可以隐藏这些功能的实现细节。
- 专用访问将实体的使用限制为封闭的声明以及同一文件中该声明的扩展名。仅在单个声明中使用特定细节的实现细节时,使用私有访问权限可以隐藏这些细节。
开放访问是最高(最低限制)访问级别,私有访问是最低(最高限制)访问级别。
开放访问仅适用于类和类成员,并且与公共访问不同,它允许模块外部的代码进行子类化和覆盖,这在下面的Subclassing中进行了讨论。明确地将一个类标记为打开表明您已经考虑了使用该类作为超类的其他模块的代码的影响,并且已相应地设计了该类的代码。
访问级别指导原则
Swift中的访问级别遵循总体指导原则:不能用具有较低(更严格)访问级别的另一个实体来定义实体。
例如:
- 不能将公共变量定义为具有内部,文件专用或私有类型,因为在使用公共变量的所有地方该类型可能都不可用。
- 函数不能具有比其参数类型和返回类型更高的访问级别,因为该函数可以在周围代码无法使用其组成类型的情况下使用。
下面将详细介绍该指导原则对语言的不同方面的具体含义。
默认访问级别
如果您自己未指定显式访问级别,则代码中的所有实体(有一些特定的例外,如本章稍后所述)都具有默认的内部访问级别。因此,在许多情况下,您无需在代码中指定显式的访问级别。
单目标应用程序的访问级别
当您编写简单的单目标应用程序时,应用程序中的代码通常是独立包含在应用程序内的,不需要在应用程序模块之外使用。内部的默认访问级别已经符合此要求。因此,您无需指定自定义访问级别。但是,您可能希望将代码的某些部分标记为私有文件或私有文件,以便在应用程序模块内的其他代码中隐藏其实现细节。
框架的访问级别
开发框架时,请将该框架的面向公众的接口标记为开放或公共,以便其他模块(例如导入该框架的应用程序)可以查看和访问它。此面向公众的接口是框架的应用程序编程接口(或API)。
笔记
框架的任何内部实现细节仍可以使用内部的默认访问级别,或者如果您想将它们从框架内部代码的其他部分隐藏起来,则可以将其标记为私有或文件私有。仅当您希望实体成为框架API的一部分时,才需要将其标记为开放或公共。
单元测试目标的访问级别
当您编写具有单元测试目标的应用程序时,需要使该应用程序中的代码可用于该模块才能进行测试。默认情况下,其他模块只能访问标记为开放或公开的实体。但是,如果您使用@testable
属性标记产品模块的导入声明并在启用测试的情况下编译该产品模块,则单元测试目标可以访问任何内部实体。
访问控制语法
通过将一个定义实体的访问级别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() {}
除非另有说明,否则默认访问级别是内部的,如默认访问级别中所述。这意味着SomeInternalClass
和someInternalConstant
可以在没有显式访问级别修饰符的情况下编写,并且仍然具有内部访问级别:
- class SomeInternalClass {} // implicitly internal
- let someInternalConstant = 0 // implicitly internal
自订类型
如果要为自定义类型指定显式访问级别,请在定义类型时执行此操作。然后,只要访问级别允许,就可以使用新类型。例如,如果定义了文件专用类,则该类只能在定义了文件专用类的源文件中用作属性的类型,或用作函数参数或返回类型。
类型的访问控制级别也会影响该类型的成员(属性,方法,初始化程序和下标)的默认访问级别。如果将类型的访问级别定义为私有或文件私有,则其成员的默认访问级别也将为私有或文件私有。如果将类型的访问级别定义为内部或公共(或使用默认的内部访问级别而不显式指定访问级别),则类型成员的默认访问级别将是内部的。
重要的
公用类型默认情况下具有内部成员,而不是公用成员。如果要让类型成员公开,则必须明确地将其标记为公共成员。此要求确保您选择发布类型的面向公众的API,并避免错误地将类型的内部工作方式呈现为公共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
- }
元组类型
元组类型的访问级别是该元组中使用的所有类型中限制性最强的访问级别。例如,如果您由两种不同的类型组成一个元组,一种具有内部访问权限,另一种具有私有访问权限,则该复合元组类型的访问级别将是私有的。
笔记
元组类型没有类,结构,枚举和函数那样的独立定义。元组类型的访问级别是根据构成元组类型的类型自动确定的,因此无法明确指定。
功能类型
函数类型的访问级别被计算为函数的参数类型和返回类型中限制性最强的访问级别。如果函数的计算访问级别与上下文默认值不匹配,则必须明确指定访问级别作为函数定义的一部分。
下面的示例定义了一个名为的全局函数someFunction()
,而没有为该函数本身提供特定的访问级别修饰符。您可能希望此函数具有默认的访问级别“内部”,但事实并非如此。实际上,someFunction()
不会像下面这样编写:
- func someFunction() -> (SomeInternalClass, SomePrivateClass) {
- // function implementation goes here
- }
该函数的返回类型是一个元组类型,由上面在Custom Types中定义的两个自定义类组成。这些类中的一个被定义为内部类,另一个被定义为私有类。因此,复合元组类型的总体访问级别是私有的(元组组成类型的最小访问级别)。
由于函数的返回类型是私有的,因此必须使用private
修饰符标记函数的整体访问级别,以使函数声明有效:
- private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
- // function implementation goes here
- }
someFunction()
用public
或internal
修饰符标记的定义或使用internal的默认设置是无效的,因为该函数的公共或内部用户可能无法适当地访问该函数的返回类型中使用的私有类。
枚举类型
枚举的个别情况将自动获得与其所属的枚举相同的访问级别。您不能为单个枚举案例指定其他访问级别。
在下面的示例中,CompassPoint
枚举具有显式的public访问级别。枚举的情况下north
,south
,east
,和west
因此也有公共的访问级别:
- public enum CompassPoint {
- case north
- case south
- case east
- case west
- }
原始值和关联值
枚举定义中用于任何原始值或关联值的类型的访问级别必须至少与枚举的访问级别一样高。例如,您不能将私有类型用作具有内部访问级别的枚举的原始值类型。
嵌套类型
嵌套类型的访问级别与其包含类型相同,除非包含类型是公共的。在公共类型内定义的嵌套类型具有内部的自动访问级别。如果希望公共类型中的嵌套类型可公开使用,则必须将嵌套类型显式声明为public。
子类化
您可以将可在当前访问上下文中访问的任何类作为其子类,并且在与该子类相同的模块中对其进行定义。您还可以将在不同模块中定义的任何开放类作为子类。子类不能具有比其父类更高的访问级别-例如,您不能编写内部父类的公共子类。
此外,对于在同一模块中定义的类,您可以覆盖在特定访问上下文中可见的任何类成员(方法,属性,初始化程序或下标)。对于在另一个模块中定义的类,您可以覆盖任何打开的类成员。
覆盖可以使继承的类成员比其超类版本更易于访问。在下面的示例中,classA
是具有称为的文件专用方法的公共类someMethod()
。类B
是的子类A
,具有降低的“内部”访问权限。尽管如此,类还是B
提供了someMethod()
“内部”访问级别的覆盖,该访问级别高于的原始实现someMethod()
:
- public class A {
- fileprivate func someMethod() {}
- }
- internal class B: A {
- override internal func someMethod() {}
- }
子类成员调用访问权限比子类成员低的超类成员甚至是有效的,只要对超类成员的调用发生在允许的访问级别上下文内(即,与文件私有成员调用的父类的超类,或内部成员调用的父类的父类在同一个模块中):
- public class A {
- fileprivate func someMethod() {}
- }
- internal class B: A {
- override internal func someMethod() {
- super.someMethod()
- }
- }
由于超类A
和子类B
是在同一源文件中定义的,因此对于B
实现someMethod()
call是有效的super.someMethod()
。
常量,变量,属性和下标
常量,变量或属性不能比其类型更公开。例如,用私有类型编写公共属性是无效的。同样,下标不能比其索引类型或返回类型更公开。
如果常量,变量,属性或下标使用私有类型,则常量,变量,属性或下标也必须标记为private
:
- private var privateInstance = SomePrivateClass()
吸气剂和二传手
常量,变量,属性和下标的获取器和设置器将自动获得与其所属的常量,变量,属性或下标相同的访问级别。
您可以为setter提供比其相应的getter更低的访问级别,以限制该变量,属性或下标的读写范围。您可以指定由写作较低访问级别fileprivate(set)
,private(set)
或internal(set)
前var
或subscript
引导。
笔记
此规则适用于存储的属性以及计算的属性。即使您没有为存储的属性编写显式的getter和setter,Swift仍然会为您合成隐式的getter和setter以提供对存储属性的后备存储的访问。使用fileprivate(set)
,private(set)
和internal(set)
来更改此综合设置器的访问级别,其方式与计算属性中的显式设置器完全相同。
下面的示例定义了一个名为的结构TrackedString
,该结构跟踪字符串属性被修改的次数:
- struct TrackedString {
- private(set) var numberOfEdits = 0
- var value: String = "" {
- didSet {
- numberOfEdits += 1
- }
- }
- }
该TrackedString
结构定义了一个名为的存储的字符串属性value
,其初始值为""
(一个空字符串)。该结构还定义了一个称为的存储的整数属性numberOfEdits
,该属性用于跟踪value
被修改的次数。这个修改跟踪与执行didSet
的财产观察者value
属性,增加numberOfEdits
每次的时间value
属性被设置为一个新值。
在TrackedString
结构和value
性能不提供明确的访问级别的修正,所以他们都收到内部默认的访问级别。但是,该numberOfEdits
属性的访问级别用private(set)
修饰符标记,以指示该属性的getter仍具有默认的内部访问级别,但是该属性只能在该TrackedString
结构一部分的代码中设置。这样可以TrackedString
在numberOfEdits
内部修改属性,但是在结构定义之外使用属性时,可以将属性显示为只读属性。
如果创建TrackedString
实例并多次修改其字符串值,则可以看到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"
尽管可以numberOfEdits
从另一个源文件中查询属性的当前值,但是不能从另一个源文件中修改属性。此限制保护了TrackedString
编辑跟踪功能的实现细节,同时仍然提供了对该功能某个方面的便捷访问。
请注意,如果需要,您可以为getter和setter分配显式的访问级别。下面的示例显示了该TrackedString
结构的一个版本,其中使用显式的public访问级别定义了该结构。因此,结构的成员(包括numberOfEdits
属性)默认具有内部访问级别。numberOfEdits
通过结合public
和private(set)
访问级别修饰符,可以使结构的属性获取器公开,而其属性设置器私有:
- public struct TrackedString {
- public private(set) var numberOfEdits = 0
- public var value: String = "" {
- didSet {
- numberOfEdits += 1
- }
- }
- public init() {}
- }
初始化器
可以为自定义初始化程序分配访问级别,该访问级别应小于或等于其初始化的类型。唯一的例外是必需的初始化程序(如必需的初始化程序中所定义)。所需的初始化程序必须与其所属的类具有相同的访问级别。
与函数和方法参数一样,初始化程序的参数类型不能比初始化程序自己的访问级别更私有。
默认初始化器
如Default Initializers中所述,Swift自动为所有结构或基类提供默认的初始化程序,而没有任何参数,而该结构或基类为其所有属性提供默认值,并且本身不提供至少一个初始化程序。
默认初始化程序具有与其初始化类型相同的访问级别,除非该类型定义为public
。对于定义为的类型,public
默认初始值设定项被认为是内部的。如果要在另一个模块中使用公共参数时使用无参数初始化器来对其进行初始化,则必须自己显式提供公共无参数初始化器作为类型定义的一部分。
结构类型的默认成员级初始化程序
如果结构的任何存储属性都是私有的,则该结构类型的默认成员初始化器被视为私有的。同样,如果结构的任何存储属性是文件专用的,则初始化程序是文件专用的。否则,初始化程序的访问级别为internal。
与上面的默认初始值设定项一样,如果您希望在另一个模块中使用某个成员结构初始值设定项来初始化一个公共结构类型,则您必须自己提供一个公共成员成员初始值设定项作为类型定义的一部分。
通讯协定
如果要为协议类型分配显式访问级别,请在定义协议时进行。这使您可以创建只能在特定访问上下文中采用的协议。
协议定义内每个需求的访问级别将自动设置为与协议相同的访问级别。您不能将协议要求设置为与其支持的协议不同的访问级别。这样可以确保所有协议要求在采用该协议的任何类型上都是可见的。
笔记
如果定义公共协议,则协议的要求在实施时就需要这些要求的公共访问级别。此行为不同于其他类型,在其他类型中,公共类型定义表示该类型成员的内部访问级别。
协议继承
如果定义从现有协议继承的新协议,则新协议最多可以具有与其继承的协议相同的访问级别。例如,您不能编写从内部协议继承的公共协议。
协议一致性
一种类型可以符合一种协议,该协议具有比该类型本身更低的访问级别。例如,您可以定义可以在其他模块中使用的公共类型,但是其与内部协议的一致性只能在内部协议的定义模块中使用。
类型符合特定协议的上下文是该类型的访问级别和协议的访问级别中的最小值。例如,如果一个类型是公共的,但是它遵循的协议是内部的,则该类型对该协议的遵从性也是内部的。
当编写或扩展类型以符合协议时,必须确保每种协议要求的类型实现至少具有与该协议所遵循的类型相同的访问级别。例如,如果公共类型符合内部协议,则每个协议要求的类型实现必须至少是内部的。
笔记
在Swift中,就像在Objective-C中一样,协议一致性是全局的-类型不可能在同一程序内以两种不同方式符合协议。
扩展名
您可以在类,结构或枚举可用的任何访问上下文中扩展类,结构或枚举。在扩展中添加的任何类型成员都具有与在要扩展的原始类型中声明的类型成员相同的默认访问级别。如果扩展公共或内部类型,则添加的任何新类型成员的默认访问级别为内部。如果扩展文件专用类型,则您添加的任何新类型成员都具有文件专用的默认访问级别。如果扩展私有类型,则添加的任何新类型成员的默认访问级别均为私有。
另外,您可以使用明确的访问级别修饰符(例如private
)标记扩展,以便为扩展中定义的所有成员设置新的默认访问级别。对于单个类型成员,仍可以在扩展名中覆盖此新的默认值。
如果您使用扩展名添加协议一致性,则不能为该扩展名提供明确的访问级别修饰符。相反,协议本身的访问级别用于为扩展内的每个协议要求实现提供默认访问级别。
扩展中的私人成员
与扩展类,结构或枚举位于同一文件中的扩展的行为就像扩展中的代码已作为原始类型的声明的一部分编写一样。因此,您可以:
- 在原始声明中声明一个私有成员,然后从同一文件的扩展名访问该成员。
- 在一个扩展名中声明一个私有成员,并从同一文件的另一个扩展名访问该成员。
- 在扩展名中声明一个私有成员,然后从同一文件中的原始声明访问该成员。
此行为意味着您可以以相同的方式使用扩展来组织代码,而不管您的类型是否具有私有实体。例如,给出以下简单协议:
- protocol SomeProtocol {
- func doSomething()
- }
您可以使用扩展来添加协议一致性,如下所示:
- struct SomeStruct {
- private var privateVariable = 12
- }
- extension SomeStruct: SomeProtocol {
- func doSomething() {
- print(privateVariable)
- }
- }
泛型
通用类型或通用函数的访问级别是通用类型或函数本身的访问级别以及对其类型参数的任何类型约束的访问级别的最小值。
类型别名
出于访问控制的目的,您定义的任何类型别名都将被视为不同的类型。类型别名的访问级别可以小于或等于其别名的访问级别。例如,私有类型别名可以为私有,文件私有,内部,公共或开放类型别名,但是公共类型别名不能为内部,文件私有或私有类型别名。
笔记
此规则也适用于用于满足协议一致性的关联类型的类型别名。