在访问权限控制这块,Swift提供了5个不同的访问级别。
一、访问控制关键词
以下是从高到低排列(实体:被访问级别修饰的内容;模块:本项目或其他第三方库):
-
open
:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open
只能用在类、类成员上) -
public
:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写 -
internal
:只允许在定义实体的模块中访问,不允许在其他模块中访问 -
fileprivate
:只允许在定义实体的源文件中访问 -
private
:只允许在定义实体的封闭声明中访问
绝大部分实体默认都是internal
级别。
二、使用准则
一个实体不可以被更低访问级别的实体定义。
- 变量/常量类型
≥
变量/常量 - 参数类型/返回值类型
≥
函数 - 父类
≥
子类 - 父协议
≥
子协议 - 原类型
≥
typealias
- 原始值类型/关联值类型
≥
枚举类型 - 定义类型A时用到的其他类型
≥
类型A - ……
示例代码一(错误示例):
fileprivate class Person { }
internal var person: Person
因为internal
修饰的变量person
可以允许定义实体的模块访问(Targets
中所有的Swift
源文件),而fileprivate
又限制了Person
类型仅限在当前源文件中使用,两者造成了冲突,所以报错。
示例代码二(正确示例):
public class Person { }
internal var person: Person
总之,上面的准则中,≥
左边的访问权限一定要大于等于右边的访问权限。
三、各种类型访问级别
3.1. 元组类型
元组类型的访问级别是所有成员类型最低的那个。
示例代码:
internal struct Dog { }
fileprivate class Person { }
fileprivate var data1: (Dog, Person)
private var data2: (Dog, Person)
data1
和data2
是变量。(Dog, Person)
是元组类型,Person
比Dog
的访问级别更低,所以元组类型访问级别是fileprivate
。
3.2. 泛型类型
泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个。
示例代码:
internal class Car { }
fileprivate class Dog { }
public class Person<T1, T2> { }
fileprivate var p = Person<Car, Dog>()
泛型类型Person<Car, Dog>
的访问级别是Person
的访问级别和Car
、Dog
的访问级别中最低级别,即fileprivate
。
3.3. 成员/嵌套类型
类型的访问级别会影响成员(属性、方法、初始化器,下标)、嵌套类型的默认访问级别。
3.3.1. 类型为private
或fileprivate
一般情况下,类型为private
或fileprivate
,那么成员/嵌套类型默认也是private
或fileprivate
。
示例代码:
private class Person {
var age = 0
func run() { }
enum Seaon { case spring, summer }
}
变量age
、函数run
、嵌套类型Season
的访问级别是private
。
3.3.2. 类型为internal
或public
一般情况下,类型为internal
或public
,那么成员/嵌套类型默认是internal
。
示例代码一:
public class Person {
var age = 0
func run() { }
enum Seaon { case spring, summer }
}
变量age
、函数run
、嵌套类型Season
的访问级别是internal
。
示例代码二:
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. 示例代码一
父类的成员不能被成员作用域外定义的子类重写。
正确示例:
public class Person {
private var age: Int = 0
public class Student: Person {
override var age: Int {
set { }
get { 10 }
}
}
}
3.4.2. 示例代码二
private class Person { }
fileprivate class Student : Person { }
上面的示例代码能否编译通过?其实看情况的。
- 如果把示例代码放到最外层(
main
文件为例),编译正常。 - 如果把示例代码放到
internal
类中: - 报错:父类级别需要大于子类级别。
分析: 代码放到类中,private
修饰的Person
类被限制仅限类中访问,而子类的访问级别又大于父类,所以报错。如果代码放到最外层,不管private
还是fileprivate
,限制的作用域都是整个源文件,因此不会报错。
3.4.3. 示例代码三
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. 示例代码四
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
getter
、setter
默认自动接收它们所属环境的访问级别。
可以给setter
单独设置一个比getter
更低的访问级别,用来限制写的权限(不能单独给getter
设置权限)。
示例代码:
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. 初始化器
- 如果一个
public
类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public
的无参初始化器。因为public
类的默认初始化器是internal
级别。
示例代码一:
public class Person {
}
var p = Person()
假设上面的示例代码是动态库里面的,那么在当前项目里是无法调用Person
的无参初始化器的,因为public
修饰的类内部所有内容默认都是internal
级别。
如果要使用Person
的无参初始化器,需要显式提供无参初始化器并且使用public
以上级别修饰。
示例代码二(可以供外界使用无参初始化器):
public class Person {
public init() { }
}
var p = Person()
-
required
初始化器≥
它的默认访问级别。
示例代码:
public class Person {
fileprivate required init() {
}
}
fileprivate
必须被替换为更高级别的访问权限才可以。
- 如果结构体有
private/fileprivate
的存储实例属性,那么它们的成员初始化器也是private/fileprivate
,否则默认就是internal
。
示例代码一:
struct Point {
fileprivate var x = 0
var y = 0
}
var p = Point(x: 10, y: 20)
Point(x: 10, y: 20)
的访问级别是fileprivate
。
示例代码二:
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. 协议
协议中定义的要求(代码)自动接收协议的访问级别,不能单独设置访问级别(和枚举类似)。
协议实现的访问级别必须≥
类型的访问级别,或者≥
协议的访问级别。
示例代码一:
internal protocol Runnable {
func run()
}
fileprivate class Person : Runnable {
public func run() {
}
}
协议实现func run() {}
的访问级别≥
(取类型Person
的访问级别 和 协议Runnable
的访问级别两者最低的级别)。
示例代码二:
public protocol Runnable {
func run()
}
public class Person : Runnable {
func run() {
}
}
因为使用public
的类,类内部成员和函数都是internal
,而协议和类都是public
,所以类中函数最低级别要使用public
。
public class Person : Runnable {
public func run() {
}
}
3.9. 扩展
- 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别。
- 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样。
- 可以单独给扩展添加的成员设置访问级别。
- 不能给用于遵守协议的扩展显式设置扩展的访问级别。
- 在同一文件中的扩展,可以写成类似多个部分的类型声明。
- 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
- 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
示例代码:
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
。
示例代码:
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