assign 和 weak,weak 自动置空原理

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表中删除,最后清理对象(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。key :对象地址,value:对象的引用计数值 - 1.

引用计数相关:

weak_table_t:  存储对象弱引用(weak 引用)的全局散列表。key :对象地址,value:所有指向该对象的 weak 指针。

2) weak_table 表:根据对象地址获取到对象在 weak_table 表中的 weak_entries。

weak_table_t

weak_entries:存放 对象 与 弱引用指针数组 映射关系的

num_entries:存放的弱引用 总数

mask:可存储弱引用的容量

max_hash_displacement:最大哈希偏移值

weak_entry_t:存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用hash表。

本质上是个字典。 其中的 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 数组中删除(该对象对应的weak_entry弱引用数组表被整体移除)

4. 如weak_table 数组内无值,把  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对象个数较少。


参考:

《iOS之一起进大厂》系列-iOS属性关键字和相关的面试题 -

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

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

__weak 修饰符 -

iOS weak原理源码探究 -

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

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

你可能感兴趣的:(assign 和 weak,weak 自动置空原理)