访问控制(Access Control)
- 在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列, 实体指被访问级别修饰的内容)
- open:允许在定义实体的模块(工程中所有创建的swift文件)、其他模块(三方库)中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
- public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
- internal:只允许在定义实体的模块中访问,不允许在其他模块中访问
- fileprivate:只允许在定义实体的源文件中访问(也就是只允许在当前swift文件中访问)
- private:只允许在定义实体的封闭声明中访问(只允许在本类中访问,一般是用来修饰属性)
- 绝大部分实体默认都是internal 级别
访问级别的使用准则
- 一个实体不可以被更低访问级别的实体定义,比如 :
- 变量\常量类型 ≥ 变量\常量
上图变量类型的访问级别为 fileprivate 小于 变量的访问级别internal,所以会报错
2. 参数类型、返回值类型 ≥ 函数
3. 父类 ≥ 子类
4. 父协议 ≥ 子协议
5. 原类型 ≥ typealias
上图原类型的访问级别为 fileprivate 小于typealiad的访问级别internal,所以会报错
6. 原始值类型、关联值类型 ≥ 枚举类型
上图关联值类型的访问级别为 fileprivate 小于enum的访问级别public,所以会报错
7. 定义类型A时用到的其他类型 ≥ 类型A
8. ......
元祖类型
- 元组类型的访问级别是所有成员类型最低的那个
这里我们可以看到元祖(Dog,Person)中Dog的访问级别是internal,Person的访问级别是fileprivate,Person的访问级别较低,所以元祖的访问级别是所有成员类型最低的fileprivate
泛型类型
- 泛型类型的访问级别是 类型的访问级别 以及 所有泛型类型参数的访问级别 中最低的那个
由上图可以看出,类型的访问级别 Person 是 public,泛型类型参数的访问级别 Dog 是fileprivate,Car是interval,这三个中最小的是fileprivate,所以泛型类型的访问级别是fileprivate,等于变量p的访问级别,所以是对的
成员、嵌套类型
- 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
- 一般情况下,类型为private或fileprivate,那么成员\嵌套类型默认也是private或fileprivate
- 一般情况下,类型为internal或public,那么成员\嵌套类型默认是internal
成员的重写
- 子类重写成员的访问级别必须 ≥ 子类的访问级别,或者 ≥ 父类被重写成员的访问级别
- 父类的成员不能被成员作用域外定义的子类重写
上图的age成员属性作用域只在person类中,子类不能使用,应都写在Person类中,写成下图形式
下面的代码能否重写?
1.
当放在同一个方法体时会报错,但是都放在全局作用域中就不会报错,因为在全局作用域中private和fileprivate访问级别相同都是整个.swift文件可以访问, 如下两图:
2.
与1的情况一致,看是否在全局作用域
3.
这段代码任何情况下,包括全局作用域下也会报错,因为Dog类虽然和Person在全局作用域下的访问级别一致,都可以认为是fileprivate,但是run和age的访问级别已经设置为private,始终低于Person,所以会报错,如下图:
getter、setter
- getter、setter默认自动接收它们所属环境的访问级别
- 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限
由上图可知,age的setter访问级别,只能在本类中使用,外部无法使用,所以不可以通过setter 给age赋值
初始化器
- 如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器,因为public类的默认初始化器是internal级别
public class Person {
//显式提供public的无参初始化器
public init() {
}
}
- required初始化器 ≥ 它的默认访问级别
- 如果结构体有private\fileprivate的存储实例属性,那么它的成员初始化器也是private\fileprivate,否则默认就是internal
由上图可知,Point除了默认初始化器,其他所有的初始化器都变为private级别,不能访问
枚举类型的case
- 不能给enum的每个case单独设置访问级别
可以看出给case单独设置访问级别,只能通过接收enum的访问级别
- 每个case自动接收enum的访问级别
- public enum定义的case也是public
协议
协议中定义的(方法等)要求自动接收协议的访问级别,不能单独设置访问级别
public协议定义的(方法等)要求也是public
协议实现的(方法等)访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别
协议定义的方法实现访问级别小于协议的访问级别,所以报错
协议实现的(方法等)访问级别 ≥ 协议的访问级别,正确
协议实现的(方法等)访问级别 ≥ 类型的访问级别,正确
下面代码能编译通过么?
不能,因为Person类型里的run()方法默认的访问级别为internal,internal小于类型和协议的访问级别public,所以错误,会报错
扩展
- 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
class Person {}
fileprivate extension Person {
//这里的run()方法的访问界别默认接受扩展的访问级别,为fileprivate
func run() {}
}
- 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
class Person {}
extension Person {
//没有显示给扩展设置访问级别,run()方法的访问级别和Person类型一致
func run() {}
}
- 可以单独给扩展添加的成员设置访问级别
class Person {}
fileprivate extension Person {
//单独给run()方法设置private访问级别
private func run() {}
}
- 不能给用于遵守协议的扩展显式设置扩展的访问级别
- 在同一文件中的扩展,可以写成类似多个部分的类型声明
- 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
- 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
public class Person {
private func run0() { }
private func eat0() {
run1()
}
}
extension Person {
private func run1() { }
private func eat1() {
run0()
}
}
extension Person {
private func eat2() {
run1()
}
}
上面代码里extension中的代码可以看作是写在Person类的原声明中,所以即使是私有的,原有类和extension也可以互相访问
将方法赋值给var\let
- 方法也可以像函数那样,赋值给var、let
当 run() 是实例方法,如下:
struct Person {
var age: Int
func run(_ v: Int) {
print("func run", age, v)
}
}
// fn1类型是(Person) -> ((Int) -> ()),接收一个Person实例,返回一个函数
var fn1 = Person.run
//fn2类型(Int) -> (),接收一个Int参数,无返回值
var fn2 = fn1(Person(age: 10))
fn2(20) //func run 10 20
Person(age: 10).run(20) //func run 10 20
由上图可以看到,将方法赋值给var、let时,需要先传给var、let一个初始化的实例,再传入方法需要的参数,实际的结果与Person(age: 10).run(20) 这种方式相同
当 run() 是类方法,如下:
struct Person {
var age: Int
static func run(_ v: Int) {
print("func run", v)
}
}
//当run是类方法,fn1的类型是(Int) -> ()
var fn1 = Person.run
fn1(20) //func run 20
当实例方法和类方法同名的时候,在给var, let赋值会优先类方法,想要优先实例方法,必须指明给var, let赋值的类型为实例方法类型,如下:
struct Person {
var age: Int
func run(_ v: Int) {
print("func run", age, v)
}
static func run(_ v: Int) {
print("func run", v)
}
}
//fn类型是(int) -> (),也就是默认是类方法的run()
var fn = Person.run
//fn1类型是(Person) -> (Int) -> (),想要run是实例方法,必须指明方法类型
var fn1: (Person) -> (Int) -> () = Person.run
do设置代码块
- Swift中不能直接用大括号{ }设置代码块:
Swift中可以用do设置代码块: