从零学习Swift 13:swift中的访问级别,weak的使用,闭包循环引用以及逃逸闭包,非逃逸闭包

总结

Swfit中提供了5个不同的访问级别,按照访问权限的高低排序如下:

  1. open: 允许在定义实体的模块,其他模块访问.允许其他模块继承,重写.open 只能用在 类 , 类成员 上.

什么是模块?一个可执行文件就是一个模块,我们平常的项目就是一个模块.导入的其他动态库也是一个模块.

  1. public: 允许在定义实体的模块,其他模块访问.但是不允许其他模块继承,重写.

  2. internal: 只允许在定义实体的模块中访问,不允许其他模块访问.

  3. fileprivate: 只允许在定义实体的源文件中访问.

  4. private: 只允许在定义实体的作用域中使用.

绝大部分实体默认是 internal访问级别,也就是在当前模块都可以访问.

访问级别的使用准则:一个实体不可以被更低访问级别的实体定义

这句话什么意思呢?我的理解就是访问实体 A ,就会直接或者间接访问实体 B,所 B 的访问级别 一定要 ≥ A 的访问级别

这种情况大概分为以下几种情况:

我们针对上述几种情况分别举例说明:

  1. 变量\常量类型要 ≥ 变量\常量访问级别:

变量的访问级别是internal,而变量类型Person的访问级别是fileprivate.也就是说我们可以在整个模块的其他文件中访问变量xiaoMing却无法访问Person.显然这是矛盾的.

  1. 参数类型,返回值类型要 ≥ 函数的访问级别:

同上,可以在其他文件调用test方法,却无法在其他文件访问Person,矛盾.

  1. 父类的访问级别要 ≥ 子类的访问级别:
    因为我们访问子类时肯定也就访问了父类中的东西.所以父类的访问级别不能小于子类的访问级别.

  2. 父协议 ≥ 子协议
    我们知道协议也是可以继承的,和父类子类的情况相同.

  3. 原类型 ≥ typealias

可以在其他模块中访问MyDog,但是却无法在其他模块中访问Dog,矛盾.

  1. 原始值类型 \ 关联值类型 ≥ 枚举类型:

这个很好理解,访问枚举就会访问到枚举中的关联类型,所以关联类型的访问级别不能小于枚举的访问级别.

访问级别的水桶效应

元组和泛型的访问级别取决于所有成员中级别最低的一个:

元组的访问级别:

泛型的访问级别:

person的访问级别取决于Person , Dog , Cat中访问级别最低的一个.

成员(属性 , 方法 , 下标 , 初始化器)的访问级别
  1. 一般情况下,类型为private 或 fileprivate,那么成员也是private 或 fileprivate

  2. 一般情况下,类型为internal 或 public,那么成员默认是internal

  3. 子类重写父类的成员,必须大于子类的访问级别,或者大于父类被重写成员的访问级别.

在全局作用域下private等价于fileprivate

从上图可以看到,在test方法作用域内,父类Person的访问权限小于子类Student的访问权限,结果报错.

如果我们把PersonStudent写在方法外面,全局作用域中呢?

可以看到,在全局作用域下,private等价于fileprivate.代码并不会报错.

补充

上面说过,类型为private 或 fileprivate,那么成员也是private 或 fileprivate.但是下面这种情况就是例外:

DogStudent是写在全局作用域的.也就是说Dog的访问权限等价于fileprivate,所以Dog内部成员的访问权限跟随Dog的访问权限.所以也是fileprivate.所以能在Student内部调用.

相当于这样:

但是如果显示的写明访问权限,就不一样了:

getter,setter

getter , setter默认接受他们所属环境的访问级别,但是我们可以给setter设置更低的访问级别,用来限制写的权限.

像下面的sex跟随Person的访问级别internal:

我们可以单独给sexsetter方法设置一个更低的访问权限,禁止外部更改:

注意: setter 的访问级别可以比 getter 的访问级别低,但是 getter 的访问级别不能比 setter 的访问级别低:

初始化器
  1. 如果一个public类想在其他模块调用系统生成的无参初始化器,那么必须显示的提供public无参初始化器.因为上面已经说过类型为 public 或 internal , 成员默认是 internal.
  1. required初始化器必须 它的默认访问级别:
  1. 如果结构体有private / fileprivate的存储实例属性,那么它带有成员的初始化器也是private / fileprivate,否则默认就是internal:
枚举的访问级别:
  1. 不能给枚举的case单独设置访问级别:
  1. 每个case自动接收枚举的访问级别.如果枚举的访问级别是public,那么case的访问级别也是public,因为不能单独给case设置访问级别.
协议的访问级别:
  1. 同枚举一样,协议中定义的要求自动接收协议的访问级别,不能为协议中的成员单独设置访问级别,如果协议的访问级别是public,协议中成员的访问级别也是public

  2. 协议实现的访问级别必须 协议的访问级别 ; 或者 遵守协议实体的访问级别.

实现的访问级别必须≥ 1 , 2 中的一个
扩展的访问级别
  1. 如果显示的设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别:

扩展的run()接收扩展的访问级别fileprivate

  1. 如果没有显示的设置扩展的访问级别,扩展添加的成员的访问级别,跟随类型的访问级别:
  1. 可以单独给扩展添加的成员设置访问级别.

  2. 不能给遵守了协议的扩展,显示设置访问级别

  1. 在同一个文件中的多个扩展,可以理解为一个类的声明拆分多个部分:

相当于这样:

方法赋值给常量\变量

我们知道函数可以赋值给一个常量或者变量,然后直接调用:

函数赋值给变量

方法也可以赋值给变量\常量,只不过麻烦一些:

image.png

如图所示,Person类中有一个实例方法run,现在我们把run方法赋值给一个变量fn:

可以看到,fn的类型是接收一个Person参数,返回一个函数,返回的函数就是run函数.

直接调用fn2:

weak , unowned
  1. weak引用计数不会 +1,同 OC 一样,当实例对象销毁后,weak会自动将指针置为nil.所以weak修饰的引用必须是var的可选项.

  2. unowned:无主引用,不会对引用计数+1,当实例对象销毁后,不会将指针置为nil.类似于 OC 中的 unsafe_unretained

  3. weak 和 unowned只能用在类实例上面

  4. unowned要比weak少一些性能消耗,因为它不会置为nil

闭包的循环引用

闭包表达式默认会对外层对象产生额外的强引用,所以在使用时要格外注意:

先看一下正常情况:

再来看一下闭包引用外层对象,导致循环引用的情况:

循环引用

person强引用了fn,fn内部又对p产生了强引用,形成了循环引用.

要解决这种循环引用的问题,需要在闭包表达式的捕获列表声明weak , unowned:

如果在定义闭包属性的同时引用了self ,那么这个闭包必须是lazy的:

因为此时self还未初始化完成,不能使用self.可以把闭包定义为lazy的,等到self初始化完成后,用到闭包的时候在初始化闭包:

闭包定义为 lazy

这时候又出现了另一个错误,这是因为.编译器认为这里可能会出现循环引用,要求必须明确写出self,提醒可能会强引用self:

解决循环引用,在闭包表达式的捕获列表声明weak:

看看下面这种情况:

同样是闭包,为什么这里不用声明捕获列表,并且编译器也没有强制写明self呢?因为这里是闭包的调用,直接把闭包的结果赋值给了fn,并且看清楚这里的fnInt类型,相当于lazy var fn: Int = 5.所以并不会产生强引用.

所以,如果 lazy 属性是闭包调用的结果,那么并不会产生强引用,因为闭包调用完后,闭包的生命周期就结束了.

逃逸闭包 , 非逃逸闭包

逃逸闭包,非逃逸闭包一般都是当做参数传递给函数.

  1. 非逃逸闭包: 闭包调用发生在函数结束前,闭包调用在函数作用域内:
闭包调用在函数作用域内
  1. 逃逸闭包: 闭包调用在有可能在函数结束后调用,闭包逃离了函数作用域,需要通过@escaping声明:
逃逸闭包

声明@escaping:

GCD 的 async

GCD 传递 async函数也是一个逃逸闭包:

所以如果在async内部访问了实例成员(属性, 方法),编译器要求在async内部要显示的写出self:

因为async是一个逃逸闭包,它的调用逃离了函数的作用域,所以我们使用的时候就要考虑要不要声明闭包表达式的捕获列表:

逃逸闭包不能捕获inout参数

逃逸闭包不能捕获inout参数,我们看看下面这种情形:

逃逸闭包不能捕获 inout 参数

解读一下为什么会这样:因为逃逸闭包逃离了函数的作用域,它的调用时机不确定,有可能 test() 函数执行完毕后 , 才会调用逃逸闭包.而 test 函数调用完毕后 , age 变量已经销毁,此时逃逸闭包在访问 age 肯定就会报错.

所以,逃逸闭包不能捕获 inout 参数.

最后补充一下swift中的两个打印

swfit中有两个协议能实现自定义打印:

  1. CustomStringConvertibledescription
  2. CustomDebugStringConvertibledebugDescription

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)

这两个协议基本上没什么区别,两个协议在debugrelease模式下都能打印.但是如果两个协议都实现后,我们在控制台使用po指令打印的是CustomDebugStringConvertible的打印信息:

po 指令打印的是debug
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342