在访问控制这块,Swift提供了五个不同的访问级别(以下是从高到低排列,实体指被访问级别修饰的内容)
- open : 允许在定义实体的模块、其他模块中访问。允许其他模块进行继承、重写(open只能用在类、类成员上)
模块概念如下图所示:
TARGETS
下的Demo
就是程序编译后的可执行文件,是一个独立的模块。也就是说,在此模块中,不管是main.swift
文件还是test.swift
文件,在此模块下的任意文件用到其他任意文件中的类或者结构体等其他数据,都不用像OC中那样import
头文件。
当然,这里需要注意权限访问级别
,open
是允许所有模块都可任意访问的,不管是工程可执行模块还是系统库模块又或者是其他第三方库模块。
动态库的一些想要公开出去给别人调用的类,一般声明open 访问权限
,如下:
open class Person {}
注意:open 只能用在类上,不能用在结构体、枚举
- public : 允许在定义实体的模块、其他模块中访问,不允许其他模块继承、重写
- internal : 只允许在定义实体的模块中访问,不允许在其他模块中访问
- fileprivate : 只允许在定义实体的源文件中访问
如上面两图所示,
Person
类定义在main.swift
文件中,且用fileprivate
访问级别修饰,就不能在test.swift
等其他文件中调用,它只允许在自己的定义源文件main.swift
中调用。
- private : 只允许在定义实体的封闭声明中访问
如图所示,变量
name
被private
修饰,它的被访问权限仅限于封闭声明中,也就是大括号内。
通过上面这五个访问控制权限可以看出,Swift不同于其他语言,它是以模块、文件为单位控制访问级别权限的。
绝大部分实体默认都是internal
级别
也就是说,不写访问控制级别的修饰关键字的话,默认都是添加了
internal
,整个项目都可访问。
访问级别的使用准则
- 一个实体不可以被更低访问级别的实体定义,例如:
- 变量\常量类型
>=
变量\常量(变量类型的访问级别要大于等于变量的访问级别,同理,常量类型的访问级别要大于等于常量的访问级别)
如图所示,变量person
的类型是Person
,internal
的访问级别是高于fileprivate
,编译报错。
也就是上面说的,一个实体不可以被更低访问级别的实体定义
,internal
修饰的实体:变量person
,不能被访问级别低于它的fileprivate
修饰的类型Person
定义。
本质:其他地方访问类实例person
,意味着也要能够访问类Person
,就要保证Person
的访问级别不能低于其实例person
的访问级别,否则,访问实例的地方无法访问类信息,怎么用?很矛盾,无法正常使用
- 参数类型、返回值类型
>=
函数
同理:别的地方调用函数,必然用到函数参数和返回值,这就意味着参数类型和返回值类型的访问级别必须不低于函数的访问级别。
否则函数参数和返回值都无法访问,怎么能调用函数?
- 父类
>=
子类
- 子类重写成员的访问级别必须 >= 子类的访问级别,或者>=父类被重写成员的访问级别(也就是说大于等于子类和父类被重写成员两者中的最低的那个访问级别)
- 父类的成员不能被成员作用域外定义的子类重写(比如:
private
修饰的父类成员,不能被父类{}的子类重写,因为private
作用域仅限于父类{}内,若是将子类放到父类{}内,则是没问题的)。
- 父协议
>=
子协议
- 原类型
>=
typealias
class Person {}
public typealias MyPerson = Person
这样的是不行的,编译报错(Type alias cannot be declared public because its underlying type uses a indternal type)
Person
定义MyPerson
,也就是定义typealias
,原类型Person
的访问级别必须大于typealias
的访问级别,显然这里的Person
的默认访问级别是indternal
,是低于public
的。
- 原始值类型、关联值类型
>=
枚举类型- 定义类型A时用到的其他类型
>=
类型A- ......
元组类型
- 元组类型的访问级别是所有成员类型最低的那个
internal class Person {}
fileprivate class Cat {}
fileprivate var aa: (Person, Cat)
internal var bb: (Person, Cat) //编译报错Variable cannot be declared internal because its type uses a fileprivate type
如上代码示例,(Person, Cat)
是元组类型,它的访问级别是所有成员中类型最低的那个,也就是此元组类型的访问级别是fileprivate
Swift 的访问控制级别规定,一个实体不可以被更低访问级别的实体定义
代码中,变量aa
的访问的级别与定义它的元组类型的级别都是fileprivate
,是附和访问级别的使用准则。
而变量bb
的访问级别internal
是高于定义它的元组类型(Person, Cat)
的访问级别fileprivate
,是不符合Swift访问级别语法规定的,所以编译报错。
泛型类型
- 泛型类型的访问级别是
类型的访问级别
以及所有泛型类型参数的访问级别
中最低的那个
internal class Cat {}
fileprivate class Dog {}
public class Person<T1, T2> {}
//Person<Cat, Dog>的访问级别是fileprivate
fileprivate var p = Person<Cat, Dog>()
代码示例中,Person<Cat, Dog>
的访问级别是由类型Person
和泛型参数类型Cat
与Dog
三者中最低的那个决定的,最终确定为Dog
的访问级别fileprivate
Person<Cat, Dog>
的访问级别fileprivate
是大于等于变量p
的访问级别,编译通过。
成员、嵌套类型
- 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
- 一般情况下,类型为
private
或fileprivate
,那么成员、嵌套类型默认也是private
或fileprivate
- 一般情况下,类型为
internal
或public
,那么成员、嵌套类型默认是internal
class Test {
private class Person {}
fileprivate class Student : Person {}//编译报错Class cannot be declared fileprivate because its superclass is private
}
父类Person
的访问级别private
是低于子类Student
的fileprivate
,所以编译报错。
但是将Test
中的类放到类外面,也就是源文件中,全局作用域内,如下:
private class Person {}
fileprivate class Student : Person {}
是不报错的,编译能通过。
为什么呢?
因为
private
访问级别仅限于封闭声明中,此刻代码在全局作用域内,并没有在某个代码块中,所以,它的访问级别就是此源文件内,本质上与fileprivate
的访问级别等同了,都是在实体源文件中。
private struct Dog {
private var name: String = "Dog"
private func eat() {};
}
fileprivate struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "张三" //编译报错'name' is inaccessible due to 'private' protection level
dog.eat() //编译报错'eat' is inaccessible due to 'private' protection level
}
}
这段代码为什么编译报错呢?
因为
Dog
成员访问级别是private
,被限定在Dog
作用域内了,外部是没有权限访问的。
private struct Dog {
var name: String = "Dog"
func eat() {};
}
fileprivate struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "张三"
dog.eat()
}
}
此段代码编译通过,为什么呢?前面不是说了类型为private或fileprivate,那么成员、嵌套类型默认也是private或fileprivate
的吗?
既然Dog
访问级别为private
,那么它的成员name
和eat()
的访问级别也应该是private
。
为什么编译通过了呢?
因为全局作用域内,private
跟fileprivate
的访问权限一样,都是此源文件内。
那么,private
就是fileprivate
,上面代码就等同于下面代码:
fileprivate struct Dog {
fileprivate var name: String = "Dog"
fileprivate func eat() {};
}
fileprivate struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "张三"
dog.eat()
}
}
注意:睁大眼睛,不要被
private
轻易迷惑,要看作用域的。
直接在全局作用域下定义的private
等价于fileprivate
再看下边代码:
class Test {
private struct Dog {
var name: String = "Dog"
func eat() {};
}
private struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "张三"
dog.eat()
}
}
}
此刻,Dog
不在全局作用域了,按说,它的属性name
和方法eat()
被默认修饰为private
,不能被外部访问的,也就是Cat
内无法调用dog.name 和 dog.eat()
的。
为什么没有编译出错呢?
因为,
Dog
的访问级别private
是限定在类Test
内的,它的成员继承了它的private
,这个private
是限定在类Test
内的private
,不是其他private
。
也就是说,成员的private
和Dog
的private
是一样的,都可以被Test
作用域内访问。
若是手动明确添加写上访问限定符private var name: String = "Dog"
,那么这里的private
和Dog
的private
是不一样的,它是明确告诉编译器,这里的属性name
是不能被外部访问的。
PS:有点绕,只可意会不可言传,就当是基因遗传吧。
getter、setter
- getter、setter默认自动接收它们所属环境的访问级别
可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限
//其他文件可以获取全局变量num的值,但是不能修改其值,只能在此文件内可修改值
fileprivate(set) public var num = 10
class Person {
//age的setter访问级别被修饰为private,Person内部可访问,Person外部无法访问
private(set) var age = 0
//weight的setter访问级别被修饰为fileprivate,main文件内部可访问,main文件外部无法访问
fileprivate(set) public var weight: Int {
set {}
get { 10 }
}
//subscript的setter访问级别被修饰为internal,模块内可访问,模块外无法访问
internal(set) public subscript(index: Int) -> Int {
set {}
get { index }
}
}
var p: Person = Person()
p.age = 22 //编译报错Cannot assign to property: 'age' setter is inaccessible
如上面代码,p.age = 22
编译报错,因为存储属性age
的setter
访问级别被修饰为private
,所以,age
的写操作仅限于Person
内部,其他地方无法使用写操作,也就不能修改其值。
由于age
的getter
没有被明确修饰为某个访问级别,默认是Person
的访问级别,也就是internal
,所以,读操作,模块内任何地方都是可以访问调用的。
注意:
getter
访问级别是不能比setter
低的,上面案例中如private(set)
这样的操作,只能用于setter
上,不能用此方法设置getter
的访问级别。
上面代码中,若是将private(set) var age = 0
改为public(get) var age = 0
,为什么编译会报错(Expected 'set' as subject of 'public' modifier)
因为看报错原因,编译器告诉我们,这种单独设置读写的访问权限的方式,它只能用于set
,且访问级别不能低于get
的访问级别。
初始化器
- 如果一个
public
类 想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public
的无参初始化器
因为
public
类 的默认初始化器是internal
级别
public class Cat {
var name: String?
}
Cat
类的访问级别是public
,其内部成员(属性、方法、下标)的访级别默认都是internal
,也就是说默认的初始化器的访问级别也是internal
。
所以,其他另一个模块是无法访问此类默认初始化器的。
required
初始化器 >= 它的默认访问级别如果结构体有
private/fileprivate
的存储实例属性,那么它的成员初始化器(带有成员的初始化器)也是private/fileprivate
,否则默认就是internal
public struct Cat {
private var name = "Cat"
var age = 1
}
//编译报错,因为name的访问级别被限定在Cat{}内部,外部无法访问
var cat = Cat(name: "张三", age: 22)
还是要注意访问级别的问题,默认初始化器的访问级别是
internal
,但是存储属性的访问级别是private/fileprivate
了,默认初始化器的访问级别也就是private/fileprivate
了,private
是在Cat类外无法调用默认初始化器的,fileprivate
可以在源文件其他地方任意调用Cat类的默认初始化器,但是其他文件没有权限调用。
枚举类型的case
- 不能给
enum
的每个case
单独设置访问级别 - 每个
case
自动接收enum
的访问级别
public enum
定义的case
也是public
协议
- 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
-
public
协议定义的要求(协议方法)也是public
- 协议实现的访问级别必须 >= 类型的访问级别,或者 >= 协议的访问级别
就是说,协议实现的访问级别必须是 >= 类型访问级别和协议访问级别中的最低的那个访问级别
fileprivate protocol Say {
func sayHello()
}
public struct Cat : Say {
fileprivate func sayHello() {}
}
如上代码,在Cat
类内,协议的实现sayHello() {}
访问级别fileprivate
。
类型Cat
的访问级别是public
。
协议Say
的访问级别是fileprivate
。
协议实现的访问级别是大于等于类型Cat
和协议Say
的访问级别中的最低的那个,编译通过。
public protocol Say {
func sayHello()
}
public struct Cat : Say {
func sayHello() {}
}
上面这段代码是编译不通过的,为什么?
因为前面说了,类型的访问级别是
public
,其内部成员访问级别默认都是internal
。
也就是说,func sayHello() {}
等同于internal func sayHello() {}
,协议实现的访问级别internal
低于public
,编译报错。
扩展
- 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
//main.swift 文件
class Cat {}
private extension Cat {
func sayHello() {}
}
//test.swift 文件
var cat = Cat()
cat.sayHello()
//编译报错'sayHello' is inaccessible due to 'fileprivate' protection level
- 如上面两段代码,在
test.swift
文件中调用,编译报错,因为访问级别不够。main.swift
文件中,Cat
扩展的访问级别是private
,所以编译器报错'sayHello' is inaccessible due to 'fileprivate' protection level
。
此处编译器报错中提示的是fileprivate
,因为全局作用域内,private
等同于fileprivate
。- 所以,猜测,应该是编译器做了优化,凡是全局作用域的
private
,就按fileprivate
处理了。
如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别跟直接在类型中定义的成员一样
可以单独给扩展添加的成员设置访问级别
不能给用于遵守协议的扩展显式设置扩展的访问级别
在同一文件中的扩展,可以写成类似多个部分的类型声明
class Cat {
private func say0() {}
private func eat0() {
say1()
}
}
extension Cat {
private func say1() {}
private func eat1() {
eat0()
}
}
extension Cat {
private func eat2() {
say1()
}
}
编译通过,为什么呢?私有private
成员的访问级别不都是限定在当前定义的{}内吗?
其实,同一文件内的后面两个扩展相当于将类Cat
拆分成不同部分,也就是说,上面的代码等同于下面的代码:
class Cat {
private func say0() {}
private func eat0() {
say1()
}
private func say1() {}
private func eat1() {
eat0()
}
private func eat2() {
say1()
}
}
- 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
- 在扩展中声明的一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
将方法赋值给let、var
- 方法也可以像函数那样赋值给一个
let
或者var
class Cat {
func say(_ str: String) {
print("Cat say:", str)
}
static func eat(_ food: String) {
print("Cat eat:", food)
}
}
let cat = Cat()
var fn = cat.say //fn类型为:(String) ->()
fn("呵呵")
//打印结果Cat say: 呵呵
var fn1 = Cat.say //fn1类型为: (Cat) ->(String) ->()
var fn2 = fn1(Cat()) //fn2类型为:(String) ->()
fn2("哈哈")
//打印结果Cat say: 哈哈
var fn3 = Cat.eat
fn3("土豆") //fn3类型为:(String) ->()
//打印结果Cat eat: 土豆