Swift小知识点之 open,public,internal,fileprivate,private访问修饰符
一,概述
访问控制限制其他源文件和模块对你的代码的访问。这个特性允许你隐藏代码的实现细节,并指定一个偏好的接口让其他代码可以访问和使用。
你可以给特定的单个类型 (类,结构体和枚举)设置访问级别,比如说属性、方法、初始化器以及属于那些类型的下标。协议可以限制在一定的范围内使用,就像全局常量,变量,函数那样。
除了提供各种级别的访问控制,Swift 为典型场景提供默认的访问级别,减少了显式指定访问控制级别的需求。 事实上,如果你编写单目标应用程序,你可能根本不需要显式指定访问控制级别。
注意:简洁起见,代码中可以设置访问级别的部分(属性,类型,函数等)在下面的章节称为“实体”。
二,两个概念(模块和源文件)
Swift 的访问控制模型基于模块和源文件的概念。
- 模块:单一的代码分配单元。指的是
Framework
或App bundle
。在 Swift 中,可以用import
关键字引入自己的工程。在 Swift 中,
Framework
或App bundle
被作为模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成Framework
,这个Framework
在 Swift 中就被称为模块。不论它被引入到某个 App 工程或者其他的Framework
,它里面的一切(属性、函数等)都属于这个模块。
- 源文件:一个模块中的单个 Swift 源代码文件(指的是 Swift 中的
Swift File
,就是编写 Swift 代码的文件)。它通常属于一个模块。虽然通常在单独源文件中定义单个类型,但是一个源文件可以包含多个类型,在
类
中又包含函数
、属性
等类型。
三,五种访问级别(以下从高到低)
Swift 提供了五种不同的访问级别,分别是:open
、public
、internal
、fileprivate
和 private
,访问权限依次由高到低。
-
open
: 可以在定义的模块中使用,也可在其他的模块中使用,(模块相当项目的target)其他模块也可继承、重写。open
只能用在类、类成员上。打包静态库给其他项目使用时就需使用open
修饰 -
public
:可以在定义的模块中使用,也可在其他的模块中使用,但是其他模块不能继承、重写。 -
internal
: 只允许在定义的模块中访问, 不允许在其他模块中方法。通常用来隐藏文件内部实现细节。 -
fileprivate
: 只允许在定义的源文件中访问(只能在.swift文件中使用) -
private
: 只允许在定义的封闭声明中访问(例如:类中)
注意:不加任何修饰时默认是internal
。通常在设计接口时,如果只在应用程序或者框架内使用可以定义为internal
级别。
四,使用原则
不能用一个更低访问级别的实体来定义当前实体。
-
比如,以
internal
、fileprivate
或private
修饰的类型,不能定义一个public
变量,因为在public
变量使用的地方,这个类型均不可用。错误原因:
Property cannot be declared public because its type uses a private type
-
比如,函数的访问级别不能高于其参数类型和返回值类型,因为这种函数的使用场景,其组成部分的类型不可用。如:
错误原因:
sayHI:
Function must be declared private or fileprivate because its parameter uses a private type
theWinner:
Function must be declared private or fileprivate because its result uses a private type
五,默认级别
-
若无显式声明,代码中所有实体的访问级别默认为
internal
。 -
如果定义一个
public
或internal
类型,其成员的访问级别默认为internal
; -
如果定义一个
fileprivate
或private
类型,其成员的访问级别默认为fileprivate
或private
。
六,访问控制
-
自定类型
-
如果你想给自定类型指明访问级别,那就在定义时指明。只要访问级别允许,新类型就可以被使用。例如,你定义了一个 file-private 的类,它就只能在定义文件中被当作属性类型、函数参数或返回类型使用。
-
类型的访问控制级别也会影响它的成员的默认访问级别(它的属性,方法,初始化方法,下标)。如果你将类型定义为 private 或 file private 级别,那么它的成员的默认访问级别也会是 private 或file private。如果你将类型定义为 internal 或 public级别(或直接使用默认级别而不显式指出),那么它的成员的默认访问级别会是 internal 。
-
注意: public 的类型默认拥有 internal 级别的成员,而不是 public。如果你想让其中的一个类型成员是 public 的,你必须按实示例代码指明。这个要求确保类型的面向公众的 API 是你选择的,并且可以避免将类型的内部工作细节公开成 API 的失误。
///public 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 } ///Internal 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 fileprivate class SomeFilePrivateClass { // explicitly file-private class func someFilePrivateMethod() {} // implicitly file-private class member private func somePrivateMethod() {} // explicitly private class member } ///private private class SomePrivateClass { // explicitly private class func somePrivateMethod() {} // implicitly private class member }
-
-
元组类型
-
元组类型的访问级别是所有类型里最严格的。例如,如果你将两个不同类型的元素组成一个元组,一个元素的访问级别是 internal,另一个是 private,那么这个元组类型是 private 级别的。即元组类型的访问级别,与其所有组成类型中访问级别最低者相同。
-
注意: 元组类型不像类、结构体、枚举和函数那样有一个单独的定义。元组类型的访问级别会在使用的时候被自动推断出来,不需要显式指明。
-
-
函数类型
-
函数类型的访问级别由函数成员类型和返回类型中的最严格访问级别决定。如果函数的计算访问级别与上下文环境默认级别不匹配,你必须在函数定义时显式指出。
下面的例子定义了一个称为 someFunction() 的全局函数,而没有指明它的访问级别。你或许以为它会是默认的 “internal” 级别,但事实不是这样。这样的 someFuniction()是无法通过编译的:func someFunction() -> (SomeInternalClass, SomePrivateClass) {// function implementation goes here}这个函数的返回类型是一个由两个在自定义类型里定义的类组成的元组。其中一个类是 “internal” 级别的,另一个是 “private”。因此,这个元组的访问级别是“private”(元组成员的最严级别,或者说级别最低的)。
由于返回类型是 private 级别的,你必须使用 private 修饰符使其合法:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {// function implementation goes here}使用 public 或 internal 标注 someFunction() 的定义是无效的,使用默认的 internal 也是无效的,7的函数可能无法访问到 private 的函数返回值。
-
-
枚举类型
-
枚举中的独立成员自动使用该枚举类型的访问级别。你不能给独立的成员指明一个不同的访问级别。
在下面的例子中 CompassPoint 有一个指明的“public”级别。里面的成员 north , south , east , 和 west
因此是“public”:
public enum CompassPoint {case northcase southcase eastcase west}
-
-
-
原始值和关联值
枚举定义中的原始值和关联值使用的类型必须有一个不低于枚举的访问级别。例如,你不能使用一个 private 类型作为一个 internal 级别的枚举类型中的原始值类型。
-
例如:
错误原因:Enum case in an internal enum uses a private type
-
-
嵌套类型
private 级别的类型中定义的嵌套类型自动为 private 级别。fileprivate 级别的类型中定义的嵌套类型自动为 fileprivate 级别。public 或 internal 级别的类型中定义的嵌套类型自动为 internal 级别。如果你想让嵌套类型是 public 级别的,你必须将其显式指明为 public。
-
子类
你可以继承任何类只要是在当前可以访问的上下文环境中。但子类不能高于父类的访问级别,例如,你不能写一个 internal 父类的 public 子类。
而且,你可以重写任何类成员(方法,属性,初始化器或下标),只要是在确定的访问域中是可见的。
重写可以让一个继承类成员比它的父类中的更容易访问。在下例中,public 级别的类 A 有一个 fileprivate 级别的 someMethod() 函数。 B 是 A 的子类,有一个降低的“internal”级别。但是,类 B 对 someMethod() 函数进行了重写即改为“internal”级别,这比 someMethod() 的原本实现级别更高:
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 { override internal func someMethod() { super.someMethod() } }
因为父类 A 和子类 B 定义在同一个源文件中,那么 B 类可以在 someMethod() 中调用父类的 someMethod() 。
-
-
常量,变量,属性和下标
常量、变量、属性不能拥有比它们类型更高的访问级别。例如,你不能写一个public 的属性而它的类型是 private 的。类似的,下标也不能拥有比它的索引类型和返回类型更高的访问级别。
如果常量、变量、属性或下标由private类型组成,那么常量、变量、属性或下标也要被标注为 private :
private var privateInstance = SomePrivateClass()
-
-
Getters 和 Setters
-
常量、变量、属性和下标的 getter 和 setter 自动接收它们所属常量、变量、属性和下标的访问级别。
-
你可以给 setter 函数一个比相对应 getter 函数更低的访问级别以限制变量、属性、下标的读写权限。你可以通过在 var 和 subscript 的置入器之前书写 fileprivate(set) , private(set) , 或 internal(set) 来声明更低的访问级别。
-
注意:这个规则应用于存储属性和计算属性。即使你没有给一个存储属性书写一个明确的 getter 和 setter,Swift 会为你合成一个 getter 和 setter 以访问到存储属性的隐式存储。使用 fileprivate(set) , private(set) 和 internal(set) 可以改变这个合成的 setter 的访问级别,同样也可以改变计算属性的访问级别。
-
下面的例子定义了一个称为 TrackedString 的结构体,它保持追踪一个字符串属性的修改次数:
struct TrackedString { private(set) var numberOfEdits = 0 var value: String = "" { didSet { numberOfEdits += 1 } } }
TrackedString 结构体定义了一个可存储字符串的属性 value ,它又一个初始值 "" (空字符串)。这个结构图同样定义了一个可存储整数的属性 numberOfEdits ,它被用于记录 value 的修改次数。这个记录由 value 属性中的didset属性实现,它会增加 numberOfEdits 的值一旦 value 被设为一个新值。
TrackedString 结构体和 value 属性都没有显式指出访问级别修饰符,因此它们都遵循默认的 internal 级别。 numberOfEdits 属性的访问级别已经标注为 private(set) 以说明这个属性的 getter 是默认的 internal 级别,但是这个属性只能被 TrackedString 内的代码设置。这允许 TrackedString 在内部修改 numberOfEdits 属性,而且可以展示这个属性作为一个只读属性当在结构体定义之外使用时——包括 TrackedString 的扩展。
如果你创建了一个 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方法。下面的例子提供了一个定义为 public 级别的 TrackedString 结构体。结构体成员(包括 numberOfEdits 属性)因此有一个默认的 internal 级别。你可以设置 numberOfEdits 属性的getter方法为 public,setter 方法为 private 级别,通过结合 public 和 private(set) 访问级别修饰符:
public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits += 1 } } public init() {} }
-
-
-
初始化器
我们可以给自定义初始化方法设置一个低于或等于它的所属的类的访问级别。唯一的例外是必要初始化器(定义在必要初始化器)。必要初始化器必须和它所属类的访问级别一致。
就像函数和方法的参数一样,初始化器的参数类型不能比初始化方法的访问级别还低。
默认初始化器
正如默认初始化器中描述的那样,Swift 自动为任何结构体和类提供一个无参数的默认初始化方法,以给它的属性提供默认值但不会提供给初始化器自身。
默认初始化方法与所属类的访问级别一致,除非该类型定义为 public 。如果一个类定义为 public ,那么默认初始化方法为 internal 级别。如果你想一个 public 类可以被一个无参初始化器初始化当在另一个模块中使用时,你必须显式提供一个 public 的无参初始化方法。
-
-
结构体的默认成员初始化器
如果结构体的存储属性时 private 的,那么它的默认成员初始化方法就是 private 级别。如果结构体的存储属性时 file private 的,那么它的默认成员初始化方法就是 file private 级别。否则就是默认的 internal 级别。正如以上默认初始化的描述,如果你想在另一个模块中使用结构体的成员初始化方法,你必须提供在定义中提供一个 public 的成员初始化方法。
-
-
协议
如果你想给一个协议类型分配一个显式的访问级别,那就在定义时指明。这让你创建的协议可以在一个明确的访问上下文中被接受。
协议定义中的每一个要求的访问级别都自动设为与该协议相同。你不能将一个协议要求的访问级别设为与协议不同。这保证协议的所有要求都能被接受该协议的类型所见。
注意如果你定义了一个 public 的协议,该协议的规定要求在被实现时拥有一个 public 的访问级别。这个行为不同于其他类型,一个 public 的类型的成员时 internal 访问级别。
-
-
协议继承
如果你定义了一个继承已有协议的协议,这个新协议最高与它继承的协议访问级别一致。例如你不能写一个 public 的协议继承一个 internal 的协议。
-
-
-
协议遵循
类型可以遵循更低访问级别的协议。例如,你可以定义一个可在其他模块使用的 public 类型,但它就只能在定义模块中使用如果遵循一个 internal 的协议。
遵循了协议的类的访问级别取这个协议和该类的访问级别的最小者。如果这个类型是 public 级别的,它所遵循的协议是 internal 级别,这个类型就是 internal 级别的。
当你写或是扩张一个类型以遵循协议时,你必须确保该类按协议要求的实现方法与该协议的访问级别一致。例如,一个 public 的类遵循一个 internal 协议,该类的方法实现至少是 “internal” 的。
-
注意
在 Swift 和 Objective-C 中协议遵循是全局的——一个类不可能在一个程序中用不同方法遵循一个协议。
-
-
扩展
你可以在任何可访问的上下文环境中对类、结构体、或枚举进行扩展。在扩展中添加的任何类型成员都有着被扩展类型相同的访问权限。如果你扩展一个公开或者内部类型,你添加的任何新类型成员都拥有默认的内部访问权限。如果你扩展一个文件内私有的类型,你添加的任何新类型成员都拥有默认的私有访问权限。如果你扩展一个私有类型,你添加的任何新类型成员都拥有默认的私有访问权限。
或者,你可以显式标注扩展的访问级别(例如, private extension )已给扩展中的成员设置新的默认访问级别。这个默认同样可以在扩展中为单个类型成员重写。
你不能给用于协议遵循的扩展显式标注访问权限修饰符。相反,在扩展中使用协议自身的访问权限作为协议实现的默认访问权限。
-
扩展中的私有成员
在同一文件中的扩展比如类、结构体或者枚举,可以写成类似多个部分的类型声明。你可以:
- 在原本的声明中声明一个私有成员,然后在同一文件的扩展中访问它;
- 在扩展中声明一个私有成员,然后在同一文件的其他扩展中访问它;
- 在扩展中声明一个私有成员,然后在同一文件的原本声明中访问它。
这样的行为意味着你可以和组织代码一样使用扩展,无论你的类型是否拥有私有成员。比如说,假设下面这样的简单协议:
protocol SomeProtocol { func doSomething() }
你可以使用扩展来添加协议遵循,比如这样:
struct SomeStruct { private var privateVariable = 12 } extension SomeStruct: SomeProtocol { func doSomething() { print(privateVariable) } }
-
-
泛型
泛指类型和泛指函数的访问级别取泛指类型或函数以及泛型类型参数的访问级别的最小值。
-
类型别名
为实现访问控制,你定义的任何类型别名,都被视为与原类型不同的类型。类型别名的访问级别不高于原类型。规则同样适用于用于满足协议遵守的关联类型的类型别名。
上图中,
Phone
类型别名的访问级别public
高于Object
类型的internal
,报错;Wine
类型别名的访问级别private
低于Object
类型的internal
,正常;Winery
遵守Factory
协议,关联类型别名Product
的访问级别internal
高于Wine
类型别名的private
,报错。