美文网首页
assign 和 weak

assign 和 weak

作者: 小李不木 | 来源:发表于2021-07-20 18:25 被阅读0次

    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

    其数据结构:

    SideTable

    slock: 操作时,对 SideTable 加锁(自旋锁)防止其他访问。

    refcnts:对象的引用计数器,存储对象引用计数的map

    weak_table_t:  存储对象弱引用的结构体,类似于数组 

    2) weak_table 表

    weak_table_t  本质上是一个数组,其中每个 Item  为 weak_entry_t ,可以根据 对象的地址获取到它在 weak_table 表中的 index 

    其定义如下

    weak_table_t

    weak_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_initWeak

    2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。

    objc_storeWeak

    3  对象释放时:调用 clearDeallocating 等一系列函数,大概操作如下:

    1.  用对象地址作为 key 在  sidetables  中找到对象对应的  sidetable 

    2.  在  sidetable  的  weak_table 数组  中找到自己的  weak_entry ,然后遍历数组把所有的数据设为 nil

    3.  释放weak_entry对象,把 weak_entryweak_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属性关键字和相关的面试题 - 简书

    OC底层探索19-weak和assign区别浅谈 - 简书

    iOS weak的实现原理和load和initialize的区别 - 简书

    __weak 修饰符 - 简书

    iOS weak原理源码探究 - 简书

    iOS底层学习 - 内存管理之weak原理探究  👍👍 👍 

    iOS weak关键字实现原理_TuGeLe的博客-CSDN博客 👍👍 👍 👍👍

    相关文章

      网友评论

          本文标题:assign 和 weak

          本文链接:https://www.haomeiwen.com/subject/pcfhmltx.html