总结
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 - 
协议实现的访问级别必须
≥协议的访问级别 ; 或者≥遵守协议实体的访问级别. 
实现的访问级别必须≥ 1 , 2 中的一个
扩展的访问级别
- 如果显示的设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别:
 
扩展的run()接收扩展的访问级别fileprivate
- 如果没有显示的设置扩展的访问级别,扩展添加的成员的访问级别,跟随类型的访问级别:
 
- 
可以单独给扩展添加的成员设置访问级别.
 - 
不能给遵守了协议的扩展,显示设置访问级别
 
- 在同一个文件中的多个扩展,可以理解为一个类的声明拆分多个部分:
 
相当于这样:
方法赋值给常量\变量
我们知道函数可以赋值给一个常量或者变量,然后直接调用:
函数赋值给变量
方法也可以赋值给变量\常量,只不过麻烦一些:
image.png
如图所示,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初始化完成后,用到闭包的时候在初始化闭包:
闭包定义为 lazy
这时候又出现了另一个错误,这是因为.编译器认为这里可能会出现循环引用,要求必须明确写出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参数,我们看看下面这种情形:
逃逸闭包不能捕获 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的打印信息:
po 指令打印的是debug











网友评论