在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列, 实体指被访问级别修饰的内容)。
- open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写,所以只能用在类、类成员上。
- public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写。
- internal:只允许在定义实体的模块中访问,不允许在其他模块中访问,直接在一个文件中定义的实体默认就是internal级别。
- fileprivate:只允许在定义实体的源文件中访问。
- private:只允许在定义实体的封闭声明中访问。
1. 嵌套类型、成员
我们知道,直接在一个文件中定义的类型的访问级别默认是internal,类型的访问级别会影响成员(属性、方法、初始化器、下标),如下:
- ⼀般情况下,类型为open,那么成员、嵌套类型默认是open。
- 一般情况下,类型为public或internal,那么成员、嵌套类型默认是internal。
- 一般情况下,类型为fileprivate或private,那么成员、嵌套类型默认也是fileprivate或private。
public class PublicClass { //外面是public
public var p1 = 0 // public
var p2 = 0 // internal
fileprivate func f1() {} // fileprivate
private func f2() {} // private
}
class InternalClass { //默认是internal
var p = 0 // internal
fileprivate func f1() {} // fileprivate
private func f2() {} // private
}
fileprivate class FilePrivateClass { //外面是fileprivate
func f1() {} // fileprivate
private func f2() {} // private
}
private class PrivateClass { //外面是private
func f() {} // private
}
补充:对于上面的第1点,有个小细节说明,如下:
class Test {
private struct Dog {
var age: Int = 0 //没手动加private
func run() {} //没手动加private
}
private struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}
}
上面代码不报错,但是下面代码就报错了。
class Test {
private struct Dog {
private var age: Int = 0 //手动加private
private func run() {} //手动加private
}
private struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run() //报错:run' is inaccessible due to 'private' protection level
dog.age = 1 //报错:age' is inaccessible due to 'private' protection level
}
}
}
解释:
- 对于第一段代码,Dog是private的,所以Dog只能在Test{}里面访问,由于age、run()没有使用任何修饰词,所以age、run()默认也是private的,但是这个private不是只能在Dog{}里面访问,而是默认跟随Dog只能在Test{}里面访问,所以第一段代码不报错。
- 对于第二段代码,由于我们手动给age、run()加上了private,所以age、run()只能在Dog{}里面访问,所以第二段代码会报错。
2. 继承、重写
- 子类的访问级别必须小于等于父类的访问级别。
如下,父类Person默认访问级别是internal,子类访问级别是public,大于父类的访问级别了,所以会报错。如果不写(默认internal),或者改成private、fileprivate都不会报错。
class Person {} //默认internal
public class Student : Person {} //public,报错
- 子类重写成员的访问级别必须大于等于父类那个成员的访问级别。
如下,Person的访问级别默认是internal,那么它的run默认也是internal,子类Student的访问级别默认是internal,如果子类重写后run的访问级别改成fileprivate,就会报错,因为重写后的run必须大于等于internal。
class Person {
func run() {}
}
class Student : Person {
fileprivate override func run() {} //报错
}
- 父类的成员不能被成员作用域外定义的子类重写(⽐如⽤private修饰)
public class Person {
private var age : Int = 0 //父类的成员作用域是这个{}
}
public class Student : Person {
override var age : Int { //报错:Property does not override any property from its superclass
set {}
get {10}
}
}
如果放到里面就不会报错了,如下:
public class Person {
private var age: Int = 0
public class Student : Person {
override var age: Int {
set {}
get {10}
}
}
}
- 直接在文件中定义的private就相当于fileprivate
private class Person {}
fileprivate class Student : Person {}
//不报错,因为private写到外面去就相当于整个文件可以访问,这时候private就相当于fileprivate,所以上面不报错
3. 最低原则
① 元组类型
元组类型的访问级别是所有成员类型最低的那个,因为这样才能保证元祖的内容都可以访问。
如下,(Dog, Person)中Dog访问级别是internal,Person访问级别是fileprivate,所以(Dog, Person)元祖类型的访问级别是fileprivate。
internal struct Dog {}
fileprivate class Person {}
如下,data1变量的访问级别是fileprivate,等于元祖类型的访问级别,所以下面不报错:
fileprivate var data1: (Dog, Person)
② 泛型类型
泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个。
如下代码,泛型类型Person<Car, Dog>,类型的访问级别是public,泛型类型参数的访问级别是internal、fileprivate,它们之中最低的那个是fileprivate,所以整个泛型类型的访问级别是fileprivate,所以下面代码不会报错:
internal class Car {}
fileprivate class Dog {}
public class Person<T1, T2> {}
fileprivate var p = Person<Car, Dog>() //不会报错
4. 自动接收原则
① 枚举类型的case
不能给enum的每个case单独设置访问级别,每个case自动接收enum的访问级别,例如:public enum定义的case也是public。
② 协议
- 协议中定义的内容,会自动接收协议的访问级别,不能单独设置访问级别,例如:public协议定义的内容也是public。
protocol Runnable {
public func run() //不能这样写
//报错:'public' modifier cannot be used in protocols
}
- 协议实现的访问级别必须大于等于协议里那个方法的访问级别
internal protocol Runnable { //协议的访问级别internal
func run() //自动接收也是internal
}
public class Person : Runnable { //类型的访问级别public
internal func run() { //不报错
//如果上面改成fileprivate或者private就会报错
}
}
下面代码能编译通过么?
public protocol Runnable {
func run()
}
public class Person : Runnable {
func run() {} //报错
//协议定义的run方法跟随协议定义也是public,但是协议实现是internal,这肯定是不可以的
}
③ 扩展
- 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别。
- 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别跟直接在类型中定义的成员一样。
fileprivate extension Person {
func run(){ //显式设置扩展的访问级别为fileprivate,那么run的访问级别也是fileprivate
}
}
class Person {} //相当于写在这里面
extension Person {
func run(){ //没有显式设置扩展的访问级别,那么run的访问级别就和写在上面类里面没什么区别
}
}
- 可以单独给扩展添加的成员设置访问级别
extension Person {
private func run(){ //单独给扩展添加的成员设置访问级别
}
}
- 不能给用于遵守协议的扩展显式设置扩展的访问级别
protocol Runnable {}
class Person {}
//报错:'fileprivate' modifier cannot be used with extensions that declare protocol conformances
fileprivate extension Person : Runnable {
func run(){
}
}
- 在同一文件中的扩展,可以写成类似多个部分的类型声明
//下面代码都在同一个文件中
public class Person {
private func run0() {}
private func eat0() {
run1() //原来类可以访问扩展中的run1
}
}
//在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
extension Person {
private func run1() {}
private func eat1() {
run0() //扩展中可以访问原来类的run0
}
}
//在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
extension Person {
private func eat2() {
run1() //扩展中可以访问扩展的run1
}
}
上面代码,虽然方法是private的,但是在扩展和类中都可以相互调用,可以理解为把一个类的东西拆分成扩展了。
5. getter、setter
getter、setter默认自动接收它们所属环境的访问级别,可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限。
fileprivate(set) public var num = 10
//setter是fileprivate,getter是public
class Person { //默认是internal
private(set) var age = 0 //setter是private,getter是默认internal
fileprivate(set) public var weight: Int {
set {}
get { return 10 }
}
internal(set) public subscript(index: Int) -> Int {
set {}
get { return index }
}
}
var p = Person()
p.age = 10 //报错:Cannot assign to property: 'age' setter is inaccessible
print(p.age)
//可以发现,读不报错,写报错,因为读是internal,写是private
6. 初始化器
- 如果一个模块想调用另一个模块public类在编译生成的默认无参初始化器,那么这个public类必须显式提供public的无参初始化器,因为这个public类的默认初始化器是internal级别的。
//你自己写的动态库
public class Person {
public init(){ //需要加public
}
}
//其他模块调用
var p = Person()
- required初始化器访问级别必须要大于等于它的默认访问级别,因为如果小于它的默认访问级别,子类就没法重写了。
public class Person {
private required init() {}
//报错:类型为public,成员默认就是internal,这时候required初始化器访问级别(private)小于它的默认访问级别(internal),所以会报错
//删除private,默认是internal,就不报错
}
- 如果结构体有private、fileprivate的存储实例属性,那么它的成员初始化器也是private、fileprivate(不然没法初始化了),否则默认就是internal。
struct Point {
fileprivate var x = 0 //如果加上fileprivate
var y = 0
}
var p = Point(x: 10, y: 20)
//不报错
//上面加上fileprivate,这时候Point(x: 10, y: 20)初始化器也是fileprivate的,如果不加默认就是internal
struct Point {
private var x = 0 //如果加上private
var y = 0
}
var p = Point(x: 10, y: 20)
//报错:'Point' initializer is inaccessible due to 'private' protection level
//如果加上private,那么Point(x: 10, y: 20)初始化器也是private的,所以上面会报错