Swfit
中提供了5个不同的访问级别,按照访问权限的高低排序如下:
-
open
: 允许在定义实体的模块,其他模块访问.允许其他模块继承,重写.open 只能用在 类 , 类成员 上.
什么是模块?一个可执行文件就是一个模块,我们平常的项目就是一个模块.导入的其他动态库也是一个模块.
public
: 允许在定义实体的模块,其他模块访问.但是不允许其他模块继承,重写.internal
: 只允许在定义实体的模块中访问,不允许其他模块访问.fileprivate
: 只允许在定义实体的源文件中访问.private
: 只允许在定义实体的作用域中使用.
绝大部分实体默认是 internal
访问级别,也就是在当前模块都可以访问.
访问级别的使用准则:一个实体不可以被更低访问级别的实体定义
这句话什么意思呢?我的理解就是访问实体 A ,就会直接或者间接访问实体 B,所 B 的访问级别 一定要 ≥ A 的访问级别
这种情况大概分为以下几种情况:
我们针对上述几种情况分别举例说明:
- 变量\常量类型要 ≥ 变量\常量访问级别:
变量的访问级别是internal
,而变量类型Person
的访问级别是fileprivate
.也就是说我们可以在整个模块的其他文件中访问变量xiaoMing
却无法访问Person
.显然这是矛盾的.
- 参数类型,返回值类型要 ≥ 函数的访问级别:
同上,可以在其他文件调用test
方法,却无法在其他文件访问Person
,矛盾.
父类的访问级别要 ≥ 子类的访问级别:
因为我们访问子类时肯定也就访问了父类中的东西.所以父类的访问级别不能小于子类的访问级别.父协议 ≥ 子协议
我们知道协议也是可以继承的,和父类子类的情况相同.原类型 ≥ typealias
可以在其他模块中访问MyDog
,但是却无法在其他模块中访问Dog
,矛盾.
- 原始值类型 \ 关联值类型 ≥ 枚举类型:
这个很好理解,访问枚举就会访问到枚举中的关联类型,所以关联类型的访问级别不能小于枚举的访问级别.
访问级别的水桶效应
元组和泛型的访问级别取决于所有成员中级别最低的一个:
元组的访问级别:
泛型的访问级别:
person
的访问级别取决于Person , Dog , Cat
中访问级别最低的一个.
成员(属性 , 方法 , 下标 , 初始化器
)的访问级别
一般情况下,类型为
private 或 fileprivate
,那么成员也是private 或 fileprivate
一般情况下,类型为
internal 或 public
,那么成员默认是internal
子类重写父类的成员,必须大于子类的访问级别,或者大于父类被重写成员的访问级别.
在全局作用域下private
等价于fileprivate
从上图可以看到,在test
方法作用域内,父类Person
的访问权限小于子类Student
的访问权限,结果报错.
如果我们把Person
和Student
写在方法外面,全局作用域中呢?
可以看到,在全局作用域下,private
等价于fileprivate
.代码并不会报错.
补充
上面说过,类型为private 或 fileprivate
,那么成员也是private 或 fileprivate
.但是下面这种情况就是例外:
Dog
和Student
是写在全局作用域的.也就是说Dog
的访问权限等价于fileprivate
,所以Dog
内部成员的访问权限跟随Dog
的访问权限.所以也是fileprivate
.所以能在Student
内部调用.
相当于这样:
但是如果显示的写明访问权限,就不一样了:
getter,setter
getter , setter
默认接受他们所属环境的访问级别,但是我们可以给setter
设置更低的访问级别,用来限制写的权限.
像下面的sex
跟随Person
的访问级别internal
:
我们可以单独给sex
的setter
方法设置一个更低的访问权限,禁止外部更改:
注意: setter 的访问级别可以比 getter 的访问级别低,但是 getter 的访问级别不能比 setter 的访问级别低
:
初始化器
- 如果一个
public
类想在其他模块调用系统生成的无参初始化器,那么必须显示的提供public
无参初始化器.因为上面已经说过类型为 public 或 internal , 成员默认是 internal
.
-
required
初始化器必须≥
它的默认访问级别:
- 如果结构体有
private / fileprivate
的存储实例属性,那么它带有成员的初始化器也是private / fileprivate
,否则默认就是internal
:
枚举的访问级别:
- 不能给枚举的
case
单独设置访问级别:
- 每个
case
自动接收枚举的访问级别.如果枚举的访问级别是public
,那么case
的访问级别也是public
,因为不能单独给case
设置访问级别.
协议的访问级别:
同枚举一样,协议中定义的要求自动接收协议的访问级别,不能为协议中的成员单独设置访问级别,如果协议的访问级别是
public
,协议中成员的访问级别也是public
协议实现的访问级别必须
≥
协议的访问级别 ; 或者≥
遵守协议实体的访问级别.
扩展的访问级别
- 如果显示的设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别:
扩展的run()
接收扩展的访问级别fileprivate
- 如果没有显示的设置扩展的访问级别,扩展添加的成员的访问级别,跟随类型的访问级别:
可以单独给扩展添加的成员设置访问级别.
不能给遵守了协议的扩展,显示设置访问级别
- 在同一个文件中的多个扩展,可以理解为一个类的声明拆分多个部分:
相当于这样:
方法赋值给常量\变量
我们知道函数可以赋值给一个常量或者变量,然后直接调用:
方法也可以赋值给变量\常量,只不过麻烦一些:
如图所示,Person
类中有一个实例方法run
,现在我们把run
方法赋值给一个变量fn
:
可以看到,fn
的类型是接收一个Person
参数,返回一个函数,返回的函数就是run
函数.
直接调用fn2
:
weak , unowned
weak
引用计数不会+1
,同 OC 一样,当实例对象销毁后,weak
会自动将指针置为nil
.所以weak
修饰的引用必须是var
的可选项.unowned
:无主引用,不会对引用计数+1
,当实例对象销毁后,不会将指针置为nil
.类似于 OC 中的unsafe_unretained
weak 和 unowned
只能用在类实例上面unowned
要比weak
少一些性能消耗,因为它不会置为nil
闭包的循环引用
闭包表达式默认会对外层对象产生额外的强引用,所以在使用时要格外注意:
先看一下正常情况:
再来看一下闭包引用外层对象,导致循环引用的情况:
person
强引用了fn
,fn
内部又对p
产生了强引用,形成了循环引用.
要解决这种循环引用的问题,需要在闭包表达式的捕获列表声明weak , unowned
:
如果在定义闭包属性的同时引用了self
,那么这个闭包必须是lazy
的:
因为此时self
还未初始化完成,不能使用self
.可以把闭包定义为lazy
的,等到self
初始化完成后,用到闭包的时候在初始化闭包:
这时候又出现了另一个错误,这是因为.编译器认为这里可能会出现循环引用,要求必须明确写出self
,提醒可能会强引用self
:
解决循环引用,在闭包表达式的捕获列表声明weak
:
看看下面这种情况:
同样是闭包,为什么这里不用声明捕获列表,并且编译器也没有强制写明self
呢?因为这里是闭包的调用,直接把闭包的结果赋值给了fn
,并且看清楚这里的fn
是Int
类型,相当于lazy var fn: Int = 5
.所以并不会产生强引用.
所以,如果 lazy 属性是闭包调用的结果,那么并不会产生强引用,因为闭包调用完后,闭包的生命周期就结束了.
逃逸闭包 , 非逃逸闭包
逃逸闭包,非逃逸闭包一般都是当做参数传递给函数.
- 非逃逸闭包: 闭包调用发生在函数结束前,闭包调用在函数作用域内:
- 逃逸闭包: 闭包调用在有可能在函数结束后调用,闭包逃离了函数作用域,需要通过
@escaping
声明:
声明@escaping
:
GCD 的 async
GCD 传递 async
函数也是一个逃逸闭包:
所以如果在async
内部访问了实例成员(属性, 方法),编译器要求在async
内部要显示的写出self
:
因为async
是一个逃逸闭包,它的调用逃离了函数的作用域,所以我们使用的时候就要考虑要不要声明闭包表达式的捕获列表:
逃逸闭包不能捕获inout
参数
逃逸闭包不能捕获inout
参数,我们看看下面这种情形:
解读一下为什么会这样:因为逃逸闭包逃离了函数的作用域,它的调用时机不确定,有可能 test() 函数执行完毕后 , 才会调用逃逸闭包.而 test 函数调用完毕后 , age 变量已经销毁,此时逃逸闭包在访问 age 肯定就会报错.
所以,逃逸闭包不能捕获 inout 参数.
最后补充一下swift
中的两个打印
swfit
中有两个协议能实现自定义打印:
-
CustomStringConvertible
的description
-
CustomDebugStringConvertible
的debugDescription
class Person: CustomStringConvertible,CustomDebugStringConvertible{
var description: String
var debugDescription: String
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
self.description = "release person's name \(name) , age is \(age)"
self.debugDescription = "degub person's name \(name) , age is \(age)"
}
}
var xiaoMing = Person(name: "小明", age: 18)
print(xiaoMing)
debugPrint(xiaoMing)
这两个协议基本上没什么区别,两个协议在debug
和release
模式下都能打印.但是如果两个协议都实现后,我们在控制台使用po
指令打印的是CustomDebugStringConvertible
的打印信息: