【iOS】Swift系列二十三 - 访问控制

在访问权限控制这块,Swift提供了5个不同的访问级别。

一、访问控制关键词

以下是从高到低排列(实体:被访问级别修饰的内容;模块:本项目或其他第三方库):

  • open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
  • public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
  • internal:只允许在定义实体的模块中访问,不允许在其他模块中访问
  • fileprivate:只允许在定义实体的源文件中访问
  • private:只允许在定义实体的封闭声明中访问

绝大部分实体默认都是internal级别。

二、使用准则

一个实体不可以被更低访问级别的实体定义。

  • 变量/常量类型 变量/常量
  • 参数类型/返回值类型 函数
  • 父类 子类
  • 父协议 子协议
  • 原类型 typealias
  • 原始值类型/关联值类型 枚举类型
  • 定义类型A时用到的其他类型 类型A
  • ……

示例代码一(错误示例):

1
2
fileprivate class Person { }
internal var person: Person

因为internal修饰的变量person可以允许定义实体的模块访问(Targets中所有的Swift源文件),而fileprivate又限制了Person类型仅限在当前源文件中使用,两者造成了冲突,所以报错。

示例代码二(正确示例):

1
2
public class Person { }
internal var person: Person

总之,上面的准则中,左边的访问权限一定要大于等于右边的访问权限。

三、各种类型访问级别

3.1. 元组类型

元组类型的访问级别是所有成员类型最低的那个。

示例代码:

1
2
3
4
internal struct Dog { }
fileprivate class Person { }
fileprivate var data1: (Dog, Person)
private var data2: (Dog, Person)

data1data2是变量。(Dog, Person)是元组类型,PersonDog的访问级别更低,所以元组类型访问级别是fileprivate

3.2. 泛型类型

泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别最低的那个。

示例代码:

1
2
3
4
internal class Car { }
fileprivate class Dog { }
public class Person<T1, T2> { }
fileprivate var p = Person<Car, Dog>()

泛型类型Person<Car, Dog>的访问级别是Person的访问级别和CarDog的访问级别中最低级别,即fileprivate

3.3. 成员/嵌套类型

类型的访问级别会影响成员(属性、方法、初始化器,下标)、嵌套类型的默认访问级别。

3.3.1. 类型为privatefileprivate

一般情况下,类型为privatefileprivate,那么成员/嵌套类型默认也是privatefileprivate

示例代码:

1
2
3
4
5
private class Person {
var age = 0
func run() { }
enum Seaon { case spring, summer }
}

变量age、函数run、嵌套类型Season的访问级别是private

3.3.2. 类型为internalpublic

一般情况下,类型为internalpublic,那么成员/嵌套类型默认是internal

示例代码一:

1
2
3
4
5
public class Person {
var age = 0
func run() { }
enum Seaon { case spring, summer }
}

变量age、函数run、嵌套类型Season的访问级别是internal

示例代码二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PublicClass { // public
public var p1 = 0 // internal
var p2 = 0 // internal
fileprivate func f1() { } // fileprivate
private func f2() { } // private
}

class InternalClass { // internal
var p = 9 // 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
}

3.4. 成员的重写

子类重写的成员访问级别必须 子类的访问级别,或者父类被重写成员的访问级别)。

3.4.1. 示例代码一

父类的成员不能被成员作用域外定义的子类重写。

正确示例:

1
2
3
4
5
6
7
8
9
public class Person {
private var age: Int = 0
public class Student: Person {
override var age: Int {
set { }
get { 10 }
}
}
}

3.4.2. 示例代码二

1
2
private class Person { }
fileprivate class Student : Person { }

上面的示例代码能否编译通过?其实看情况的。

  1. 如果把示例代码放到最外层(main文件为例),编译正常。

  2. 如果把示例代码放到internal类中:

    报错:父类级别需要大于子类级别。

分析: 代码放到类中,private修饰的Person类被限制仅限类中访问,而子类的访问级别又大于父类,所以报错。如果代码放到最外层,不管private还是fileprivate,限制的作用域都是整个源文件,因此不会报错。

3.4.3. 示例代码三

1
2
3
4
5
6
7
8
9
10
11
private struct Dog {
var age: Int = 0
func run() { }
}
fileprivate struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}

和示例代码一都是一致的,放到类中报错,放到源文件根层就编译正常。

但是如果上面的代码中Dog成员变量和内部函数都加上private访问限制会怎样呢?

报错:

分析:因为Dog的内部成员变量和函数都是用private控制的,所以仅限在Dog内部使用,外部无法访问。

直接在全局作用域下定义的private等价于fileprivate

3.4.4. 示例代码四

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test {
private struct Dog {
var age: Int = 0
func run() { }
}
private struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run()
dog.age = 1
}
}
}

Dog虽然被private修饰,但是Person中还是可以使用Dog及其内部属性的。Dog中的属性访问权限默认跟随类型是private,但是没有明确声明访问权限时,内部属性的作用域和类型相同。所以在Person中可以使用Dog的内部属性。

3.5. getter和setter

gettersetter默认自动接收它们所属环境的访问级别。

可以给setter单独设置一个比getter更低的访问级别,用来限制写的权限(不能单独给getter设置权限)。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
fileprivate(set) public var num = 10
class Person {
private(set) var age = 0
fileprivate(set) public var weight: Int {
set { }
get { 10 }
}
internal(set) public subscript(index: Int) -> Int {
set { }
get { index }
}
}

3.6. 初始化器

  1. 如果一个public想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器。因为public的默认初始化器是internal级别。

示例代码一:

1
2
3
4
public class Person {

}
var p = Person()

假设上面的示例代码是动态库里面的,那么在当前项目里是无法调用Person的无参初始化器的,因为public修饰的类内部所有内容默认都是internal级别。

如果要使用Person的无参初始化器,需要显式提供无参初始化器并且使用public以上级别修饰。

示例代码二(可以供外界使用无参初始化器):

1
2
3
4
public class Person {
public init() { }
}
var p = Person()
  1. required初始化器 它的默认访问级别。

示例代码:

1
2
3
4
5
public class Person {
fileprivate required init() {

}
}

fileprivate必须被替换为更高级别的访问权限才可以。

  1. 如果结构体有private/fileprivate的存储实例属性,那么它们的成员初始化器也是private/fileprivate,否则默认就是internal

示例代码一:

1
2
3
4
5
struct Point {
fileprivate var x = 0
var y = 0
}
var p = Point(x: 10, y: 20)

Point(x: 10, y: 20)的访问级别是fileprivate

示例代码二:

1
2
3
4
5
struct Point {
private var x = 0
var y = 0
}
var p = Point(y: 20)

只要有一个存储属性是private,所有成员初始化器都不能使用(编译器也不再自动生成),只能使用无参初始化器。

3.7. 枚举类型的case

不能给enum的每个case单独设置访问级别。

每个case自动接收enum的访问级别。

public enum定义的case也是public(和结构体/类不一样)。

3.8. 协议

协议中定义的要求(代码)自动接收协议的访问级别,不能单独设置访问级别(和枚举类似)。

协议实现的访问级别必须类型的访问级别,或者协议的访问级别。

示例代码一:

1
2
3
4
5
6
7
8
9
internal protocol Runnable {
func run()
}

fileprivate class Person : Runnable {
public func run() {

}
}

协议实现func run() {}的访问级别(取类型Person的访问级别 和 协议Runnable的访问级别两者最低的级别)。

示例代码二:

1
2
3
4
5
6
7
8
9
public protocol Runnable {
func run()
}

public class Person : Runnable {
func run() {

}
}

因为使用public的类,类内部成员和函数都是internal,而协议和类都是public,所以类中函数最低级别要使用public

1
2
3
4
5
public class Person : Runnable {
public func run() {

}
}

3.9. 扩展

  1. 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别。

  2. 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样。

  3. 可以单独给扩展添加的成员设置访问级别。

  4. 不能给用于遵守协议的扩展显式设置扩展的访问级别。

  5. 在同一文件中的扩展,可以写成类似多个部分的类型声明。

    • 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
    • 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()
}
}

可以理解为,只要在同一个文件中的类扩展,其实就是把这个类拆分出多个声明而已。

3.10. 将方法赋值给var/let

方法也可以像函数那样,赋值给一个let或者var

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Person {
var age: Int
func run(_ v: Int) {
print("func run", age, v)
}
static func run(_ v: Int) {
print("static func run", v)
}
}

let fn1 = Person.run
fn1(10) // 输出:static func run 10

let fn2: (Int) -> () = Person.run
fn2(20) // 输出:static func run 20

// 实例传给变量
let fn3: (Person) -> ((Int) -> ()) = Person.run
fn3(Person(age: 18))(30) // 输出:func run 18 30