MRC时代使用unsafe_unretained。ARC时候才有weak。
1、assign可以修饰对象和基本数据类型( int, BOOL ), weak只修饰对象
2、assign 所修饰的对象被释放后,还会指向原对象内存地址, 会产生悬垂指针 。weak 所修饰的对象被废弃之后,weak 所修饰对象会被设置为nil。
对象在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
assign 和 weak 都不会改变修饰对象的引用计数。
unsafe_unretained也可能产生野指针,所以它名字是"unsafe”
MRC中使用retain、release ,assign 。weak和strong就只能在ARC中使用
weak其实类似于assign,叫弱引用,也是不增加引用计数。一般只有在防止循环引用时使用,比如父类引用了子类,子类又去引用父类。IBOutlet、Delegate一般用的就是weak。
weak 和 assign 的区别在于 weak在对象没有引用的时候会自动进行置空操作,引用计数变成0 时会把指针置为nil ,不会出现野指针错误,但是assign 在对象释放之后,指向对象的指针还是存在的,此时再对这个指针进行操作就会出现崩溃,野指针错误。
相对的,strong就类似与retain了,叫强引用,会增加引用计数,类内部使用的属性一般都是strong修饰的,现在ARC已经基本替代了MRC,所以我们最常见的就是strong了。
ARC中用是否有强指针引用来判断对象是否需要销毁,对象创建以后默认情况下全是强指针 ,加上_weak修饰的时候表示对象为弱指针。只要没有任何强指针指向对象,对象就会被销毁。
runtime对注册的类,会进行布局,对于weak对象会放入一个hash表中,用weak指向的对象内存地址作为key,当此对象的引用计数为0 的时候会dealloc,会调用clearDeallocating函数,clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
weak属性修饰的变量,如何实现在变量没有强引用后自动置为 nil ?
runtime 维护了一个弱引用表,在对象回收的时候,根据对象的地址得到所有weak指针地址的数组,遍历数组把其中的数据置为nil,释放weak_entry_t对象;并且把side_table中的对象进行clean;
一、数据结构
SideTables本质上是系统中全局唯一的 StripedMap,管理对象的引用计数和weak引用指针,每个对象在此表中都有对应的一个 SideTable。
StripedMap本质是一个数组,且在iOS系统下,容量为64。通过实 [ ] 操作,实现了类似字典的功能:可通过传入一个对象作为key值,来获取对应的Item。
在 SideTables中, Item 类型为 SideTable,对于任何一个对象, SideTables都能根据其地址对应到具体的一个 SideTable上。objTable = &SideTables()[obj] : 通过对象地址 获取到其对应的SideTable
1) SideTable
其数据结构:
SideTableslock: 操作时,对 SideTable 加锁(自旋锁)防止其他访问。
refcnts:对象的引用计数器,存储对象引用计数的map
weak_table_t: 存储对象弱引用的结构体,类似于数组
2) weak_table 表
weak_table_t 本质上是一个数组,其中每个 Item 为 weak_entry_t ,可以根据 对象的地址获取到它在 weak_table 表中的 index
其定义如下
weak_table_tweak_entries:存放 对象 与 弱引用指针数组 映射的 数组
num_entries:存放的弱引用 总数
mask:可存储弱引用的容量
max_hash_displacement:最大哈希偏移值
weak_entry_t 是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用hash表。
weak_entry_t
本质上是个字典。 其中的 key 值为对象, value 对应为一个数组,该数组最初为内部的一个大小为4 的数组 (inline_referrers ),当数组大小超过4后,则变为内部一个可变大小数组( referrers )。数组中保存的值均为 weak_referrer_t 类型的数据。
weak_entry_t 结构体referent 是引用对象。
union(联合体) 里存放着弱引用该对象的指针,union 里面的多个成员变量共享同一内存空间。union 中有两个结构体都是存储弱引用对象指针的集合。
第1个结构体referrers 是一个可进行扩容的集合,
第2个结构体中 inline_referrers 是一个容量为 4 的数组。不可扩容,弱引用指针优先存放这里
weak_entry_t 默认使用 inline_referrers 来保存弱引用指针,当此数组容量满后,会使用 referrers 接管保存工作。out_of_line_ness 便是描述存储的弱引用指针是否超出 inline_referrers 的容量。
weak_referrer_t
本质上是 objc_object **,即Objective-C对象的地址。
weak_entry_t 类型的 value 数组中,每一个 Item 均为一个地址,即weak对象的地址。
对应关系:
对应关系图1. SideTables 一对多 SideTable :每个对象都有一个SideTable与之对应
2. SideTable 一对一 weak_table :一个SideTable可能存储了多个对象的弱引用信息
3. weak_table 一对多 weak_entry :对象在weak_table中 找到自己的弱引用关系数据列表weak_entry
4. weak_entry 一对多 weak_referrer :存储每一个指向 对象的 弱引用指针地址对象,可在数组中找到自己(当前弱引用指针)所在的位置,进行删除,更新等操作
每个对象都会在全局的 SideTables 中对应至一个 SideTable中, SideTable中的 weak_table_t 记录了该 SideTable下所有内存对象的 weak 引用信息,内存对象可在 weak_table_t 中找到与自己内存地址 对应的 weak_entry_t , weak_entry_t 中记录了所有指向该内存对象且weak修饰的对象信息。
runtime 如何实现 weak 变量的自动置nil?
1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
objc_initWeak2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。
objc_storeWeak3 对象释放时:调用 clearDeallocating 等一系列函数,大概操作如下:
1. 用对象地址作为 key 在 sidetables 中找到对象对应的 sidetable
2. 在 sidetable 的 weak_table 数组 中找到自己的 weak_entry ,然后遍历数组把所有的数据设为 nil
3. 释放weak_entry对象,把 weak_entry 从 weak_table 数组中删除
4. 清理对象的记录,把 sidetable 中的对象记录进行clean;
首先,通过 weak_entry_for_referent 找到 weak_table 中的弱引用条目 entry,然后通过 remove_referrer 函数从 entry 的引用指针列表中删除 __weak变量指针。如果 entry 中没有引用指针了,那么便会执行 weak_entry_remove 从弱引用表 weak_table 中删除该弱引用条目。
如果,entry 的引用指针数不超过 inline_referrers 的容量,那么遍历 inline_referrers 找到引用指针的位置并置为nil。如果超过 inline_referrers 的容量,那么便得去 referrers 中找到引用指针置为nil并将 referrers 的长度减一。如果找不到便会调用 objc_weak_error() , 此函数将弱引用条目 entry 从 weak_table 中删除,然后通过 weak_compact_maybe 去检查是否需要缩小 weak_table 的容量。
weak_entry_t在移除对象后,并不会进行类似 weak_table_t压缩数据结构的操作,故应尽量保证weak对象个数较少。
__weak 和 _Unsafe_Unretain 的区别?
1. weak较为安全:weak 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为 nil, _Unsafe_Unretain不会置为 nil,容易出现 悬垂指针,发生崩溃。
3. __unsafe_unretained 比 __weak效率快:使用 weak 是有代价的,通过上面的原理可知,__weak需要检查对象是否已经消亡,而为了知道是否已经消亡,自然也需要一些信息去跟踪对象的使用情况。也正因此,__unsafe_unretained 比 __weak快,所以当明确知道对象的生命期时,选择__unsafe_unretained 会有一些性能提升,这种性能提升是很微小的。但当很清楚的情况下,__unsafe_unretained 也是安全的,自然能快一点是一点。而当情况不确定的时候,应该优先选用 __weak 。
4. unowned使用在Swift中,也会分 weak 和 unowned。unowned 的含义跟 __unsafe_unretained 差不多。假如很明确的知道对象的生命期,也可以选择 unowned。
参考:
《iOS之一起进大厂》系列-iOS属性关键字和相关的面试题 - 简书
网友评论