访问级别:
Swift 提供了五种不同的访问级别,分别是::open、public 、internal、fileprivate、private,访问权限依次由高到低。
open:可以被任何模块的代码访问,包括override(重写)和继承。
open只能用在类、类成员上。
public: 可以被任何模块的代码访问,模块内是可以被override(重写)和继承的,而在但其他模块不可以override(重写)和继承。
internal:只允许在定义的模块中访问,不允许在其他模块中使用。默认访问级别,可写可不写。
fileprivate:修饰的属性或者方法只能在当前文件中访问,包括override(重写)和继承,当前类的extension 中也可以。如果一个文件中含有多个类,这些类中也可以。
private:修饰的属性或者方法只能在当前类中访问,当前类的extension 中也可以访问。如果当前文件有多个类,这些类中不可以访问。
使用原则:
不能用一个更低访问级别的实体来定义当前实体
比如,以internal、fileprivate或private修饰的类型,不能定义一个public变量。因为public变量使用的地方,这些类型均不可用:
比如,函数的访问级别不能高于其他参数类型和返回值类型,因为这种函数的使用场景,其组成部分类型不可用:
默认级别:
若无显式声明,代码中所有实体的访问级别默认为 internal。
如果定义一个 public 或 internal 类型,其成员的访问级别默认为 internal;
如果定义一个 fileprivate 或 private 类型,其成员的访问级别默认为 fileprivate 或 private。
访问控制:
自定义类型:
1、如果想给自定类型指明访问级别,那就在定义时指明。只要访问级别允许,新类型就可以被使用。
例如,你定义了一个 file-private 的类,它就只能在定义文件中被当作属性类型、函数参数或返回类型使用。
2、类型的访问控制级别也会影响它的成员的默认访问级别(它的属性,方法,初始化方法,下标)。
如果你将类型定义为 private 或 file private 级别,那么它的成员的默认访问级别也会是 private 或file private。如果将类型定义为 internal 或 public级别(或直接使用默认级别而不显式指出),那么它的成员的默认访问级别会是 internal 。
注意: public 的类型默认拥有 internal 级别的成员,而不是 public。如果想让其中的一个类型成员是 public 的你必须按实示例代码指明。这个要求确保类型的面向公众的 API 是你选择的,并且可以避免将类型的内部工作细节公开成 API 的失误。
///public
public class HFPublicClass {
public var publicProperty = 0
var internalProperty = 0
fileprivate func filePrivateMethod() {}
private func privateMethod() {}
}
///Internal
class HFInternalClass {
var internalProperty = 0
fileprivate func filePrivateMethod() {}
private func privateMethod() {}
}
///fileprivate
fileprivate class HFFilePrivateClass {
func filePrivateMethod() {}
private func privateMethod() {}
}
///private
private class HFPrivateClass {
func privateMethod() {}
}
元组类型:
元组类型的访问级别是所有类型里最严格的。
例如,如果将两个不同类型的元素组成一个元组,一个元素的访问级别是 internal,另一个是 private,那么这个元组类型是 private 级别的。
即元组类型的访问级别,与其所有组成类型中访问级别最低者相同。
函数类型:
函数类型的访问级别由函数成员类型和返回类型中的最严格访问级别决定。
如果函数的计算访问级别与上下文环境默认级别不匹配,必须在函数定义时显式指出。
例如:
这个函数的返回类型是一个由两个在自定义类型里定义的类组成的元组。
其中一个类是 “internal” 级别的,另一个是 “private”。
因此,这个元组的访问级别是“private”(元组成员的最严级别,或者说级别最低的)。
由于返回类型是 private 级别的,必须使用 private 修饰符使其合法:
private func someFunction() -> (HFInternalClass, HFPrivateClass) {
}
枚举类型:
枚举中的独立成员自动使用该枚举类型的访问级别。不能给独立的成员指明一个不同的访问级别。
public enum CompassPoint {
case north
case south
case east
case west
}
在上面的例子中 CompassPoint 有一个指明的“public”级别。
里面的成员 north , south , east , 和 west因此是“public”。
枚举定义中的原始值和关联值使用的类型必须有一个不低于枚举的访问级别。
例如,不能使用一个 private 类型作为一个 internal 级别的枚举类型中的原始值类型。
struct HFSucess {
}
private struct HFFailure {
}
enum HFResult {
case sucess(HFSucess)
case failure(HFFailure)
}
嵌套类型:
private 级别的类型中定义的嵌套类型自动为 private 级别。
fileprivate 级别的类型中定义的嵌套类型自动为 fileprivate 级别。
public 或 internal 级别的类型中定义的嵌套类型自动为 internal 级别。
如果想让嵌套类型是 public 级别的,你必须将其显式指明为 public。
子类的访问级别不能高于父类。
重写可以让一个继承类成员比它的父类中的更容易访问。例如:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
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() {
super.someMethod()
}
}
子类成员调用父类中比子类更低访问级别的成员,只要这个调用发生在一个允许的访问级别上下文中(即对 fileprivate 成员的调用要求父类在同一个源文件中,对 internal 成员的调用要求父类在同一个模块中)
因为父类 A 和子类 B 定义在同一个源文件中,那么 B 类可以在 someMethod() 中调用父类的 someMethod() 。
常量、变量、属性、下标:
常量、变量、属性不能拥有比它们类型更高的访问级别。
例如,不能写一个public 的属性而它的类型是 private 的。
类似的,下标也不能拥有比它的索引类型和返回类型更高的访问级别。
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 的扩展。
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"
创建了一个 TrackedString 的实例并修改了几次字符串的值,可以看到 numberOfEdits 属性的值更新到匹配修改的次数。
尽管可以从别的源文件中询问到 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 )已给扩展中的成员设置新的默认访问级别。这个默认同样可以在扩展中为单个类型成员重写。
不能给用于协议遵循的扩展显式标注访问权限修饰符。
相反,在扩展中使用协议自身的访问权限作为协议实现的默认访问权限。
扩展中的私有成员
在同一文件中的扩展比如类、结构体或者枚举,可以写成类似多个部分的类型声明。
可以:
在原本的声明中声明一个私有成员,然后在同一文件的扩展中访问它;
在扩展中声明一个私有成员,然后在同一文件的其他扩展中访问它;
在扩展中声明一个私有成员,然后在同一文件的原本声明中访问它。
泛型:
泛指类型和泛指函数的访问级别取泛指类型或函数以及泛型类型参数的访问级别的最小值。
类型别名:
为实现访问控制,定义的任何类型别名,都被视为与原类型不同的类型。类型别名的访问级别不高于原类型。规则同样适用于用于满足协议遵守的关联类型的类型别名。
typealias HFMaterial = Dictionary<String, Any>
class HFObject {
private(set) var info: HFMaterial
init(_ info: HFMaterial) {
self.info = info
}
}
public typealias HFPhone = HFObject
private typealias HFWine = HFObject
protocol HFFactory {
associatedtype Product
func produce(with material: HFMaterial) -> Product
}
struct HFWinery: HFFactory {
typealias Product = HFWine
func produce(with material: HFMaterial) -> HFWine {
return HFWine(material)
}
}
如图可知:
HFPhone 类型别名的访问级别 public 高于 HFObject 类型的 internal,报错;
HFWine 类型别名的访问级别 private 低于 HFObject 类型的 internal,正常;
HFWinery 遵守 HFFactory 协议,关联类型别名 Product 的访问级别 internal 高于 HFWine 类型别名的 private,报错。
参考:
Swift小知识点之 open,public,internal,fileprivate,private访问修饰符