iOS——weak修饰符的学习补充

server/2024/12/22 2:57:04/

Weak修饰符的内部机制

SideTable

ObjectC中对对象的存储,实现上做了一定的优化,一旦有弱引用对象被赋值,即运行时(Runtime)会在全局的SideTables中分配一个SideTable空间,此空间是根据对象的地址相关算法获取到的一个位置(所以存在多个对象分配到同一个位置,类似哈希冲突)。其中SideTable结构如下:

struct SideTable {//SideTable的结构spinlock_t slock;RefcountMap refcnts;weak_table_t weak_table;//SideTable() {memset(&weak_table, 0, sizeof(weak_table));}~SideTable() {_objc_fatal("Do not delete SideTable.");}void lock() { slock.lock(); }void unlock() { slock.unlock(); }void reset() { slock.reset(); }// Address-ordered lock discipline for a pair of side tables.template<HaveOld, HaveNew>static void lockTwo(SideTable *lock1, SideTable *lock2);template<HaveOld, HaveNew>static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

slock:自旋锁,用于多线程环境下的同步。
RefcountMap:引用计数映射表,管理对象的引用计数。键值为对象指针,对应value为引用的一些标记位以及状态
weak_table:弱引用表,管理对象的弱引用。是一个散列表。

请添加图片描述

我们先来看一下weak_table_t的结构:

struct weak_table_t {weak_entry_t *weak_entries;//hash数组,用来存储弱引用对象的相关信息weak_entry_t。size_t    num_entries;//hash数组中的元素个数。uintptr_t mask;//参与判断引用计数辅助量。hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)。uintptr_t max_hash_displacement;//可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)。
};

这个SideTable中,比较重要的就是RefcountMapweak_table,这俩都是用来记录引用计数的散列表。其中RefcountMap的作用是记录SideTable中对象的强引用的引用计数,而weak_table,是用于存储弱引用的,并且在必要的时候更新对象的弱引用,比如说对象被释放的时候更新该对象的所有弱引用为nil,或者当该对象的弱引用指向其他对象的时候也要更新;

我们来看一下weak_entry_t 的代码:

#define WEAK_INLINE_COUNT 4
struct weak_entry_t {DisguisedPtr<objc_object> referent; // 封装 objc_object 指针,即 weak 修饰的变量指向的对象union {struct {weak_referrer_t *referrers;uintptr_t        out_of_line : 1;  uintptr_t        num_refs : PTR_MINUS_1;    // 引用数值,这里记录弱引用表中引用有效数字,即里面元素的数量uintptr_t        mask;uintptr_t        max_hash_displacement;     // hash 元素上限阀值};struct {// out_of_line=0 is LSB of one of these (don't care which)weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];     };};
};

它使用了一种优化空间使用的方式。
当弱引用的数量不多时,使用结构体内部的一个固定大小的数组(inline_referrers)。这类似于枚举中的小数据范围直接内联存储在结构体中,避免了动态内存分配的开销。
当弱引用的数量超过固定大小时,转而使用指向动态分配内存的指针(referrers),这类似于在需要更大存储空间时,枚举或其他结构体使用指针指向外部分配的存储区域。
其中out_of_line的值通常情况下是等于零的,所以弱引用表总是一个objc_objective指针数组,当超过4时, 会变成hash表。

生命状态

一个对象被创建出来后,如果被强引用就增加SideTable中的refcnts的信息,如果被弱引用就增加weak_table的信息。如果弱引用减少,会从weak_table中删除对应的引用信息;如果是refcnts对应的对象,如果是deallocating状态,会把该对象从refcnts移除掉。所以SideTables空间作为一个全局内存,会一直存在,没有回收的概念。

Weak的工作流程

weak的实现原理概括为以下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,清理对象的记录。

Weak的初始化

runtime会调用objc_initWeak函数,objc_initWeak函数会初始化一个新的weak指针指向对象的地址。

  • objc_initWeak:
// location指针objc , newObj原始对象object
id objc_initWeak(id *location, id newObj) {// 查看原始对象实例是否有效// 无效对象直接导致指针释放if (!newObj) {*location = nil;return nil;}// 这里传递了三个 bool 数值// 使用 template 进行常量参数传递是为了优化性能return storeWeak<false/*old*/, true /*new*/, true/*crash*/>(location, (objc_object*)newObj);
}

添加引用:

objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

// HaveOld:  true - location指向了一个旧对象
//          false - location当前没有指向任何对象,或者旧对象已经被清理
// HaveNew:  true - 需要被分配的新值,当前值可能为 nil
//          false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
//          false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {// 该过程用来更新弱引用指针的指向// 初始化 previouslyInitializedClass 指针Class previouslyInitializedClass = nil;id oldObj;// 声明两个 SideTable// ① 新旧散列创建SideTable *oldTable;SideTable *newTable;// 获得新值和旧值的锁存位置(用地址作为唯一标示)// 通过地址来建立索引标志,防止桶重复// 下面指向的操作会改变旧值
retry:// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable,即获取其旧的Tableif (HaveOld) {// 更改指针,获得以 oldObj 为索引所存储的值地址oldObj = *location;oldTable = &SideTables()[oldObj];} else {  // 如果weak ptr之前没有弱引用过一个obj,则oldTable = niloldTable = nil;}// 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTableif (HaveNew) {// 更改新值指针,获得以 newObj 为索引所存储的值地址newTable = &SideTables()[newObj];} else {  // 如果weak ptr不需要引用一个新obj,则newTable = nilnewTable = nil;}// 加锁操作,防止多线程中竞争冲突SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable); // 避免线程冲突重处理// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改,需要返回上边重新处理if (HaveOld  &&  *location != oldObj) {SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);goto retry;}// 防止弱引用间死锁// 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向if (HaveNew  &&  newObj) {// 获得新对象的 isa 指针Class cls = newObj->getIsa();// 如果cls还没有初始化,先初始化,再尝试设置weakif (cls != previouslyInitializedClass  &&!((objc_class *)cls)->isInitialized()) {// 解锁SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);// 对其 isa 指针进行初始化_class_initialize(_class_getNonMetaClass(cls, (id)newObj));// 如果该类已经完成执行 +initialize 方法是最理想情况// 如果该类 +initialize 在线程中// 例如 +initialize 正在调用 storeWeak 方法// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记,防止改if分支再次进入previouslyInitializedClass = cls;// 重新获取一遍newObj,这时的newObj应该已经初始化过了goto retry;}}// ② 清除旧值//  如果之前该指针有弱引用过一个obj那就得需要清除之前的弱引用if (HaveOld) {// 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// ③ 分配新值// 如果weak_ptr需要弱引用新的对象newObjif (HaveNew) {// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中// 如果弱引用被释放 weak_register_no_lock 方法返回 nilnewObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,(id)newObj, location,CrashIfDeallocating);// (2) 更新newObj的isa的weakly_referenced bit标志位if (newObj  &&  !newObj->isTaggedPointer()) {// 弱引用位初始化操作// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用newObj->setWeaklyReferenced_nolock();}// (3)*location 赋值,也就是将weak ptr直接指向了newObj,也就是确保其指针指向是正确的。可以看到,这里并没有将newObj的引用计数+1*location = (id)newObj;}else {// 没有新值,则无需更改}// 解锁,其他线程可以访问oldTable, newTable了SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);// 返回newObj,此时的newObj与刚传入时相比,设置了weakly-referenced bit位置1return (id)newObj;
}

这段代码的作用是更新一个弱引用指向。这里先判断被更新的对象有没有指向旧的对象,要是有,就获取以该旧对象地址为key的在SideTable中的value,然后将这个value放到旧散列表oldTable中。要是没有,就把oldTable置为nil;
然后更新新散列表newTable,如果需要弱引用一个新值,就将newTable的值值为newObj在SideTable中的值,否则将newTable值为nil;
然后是一个避免线程冲突的处理,location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改,需要返回上边重新处理,并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向。
然后清除该指针的旧值:如果之前该指针有弱引用过一个obj那就得需要清除之前的弱引用,如果weak_ptr之前弱引用过别的对象oldObj,则调用 weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
然后为该指针分配新值:调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中。如果弱引用被释放 weak_register_no_lock 方法返回 nil。更新newObj的isa的weakly_referenced bit标志位。*location 赋值,也就是将weak ptr直接指向了newObj,也就是确保其指针指向是正确的。可以看到,这里并没有将newObj的引用计数+1

weak_register_no_lock:把新的对象进行注册操作,完成与对应的弱引用表进行绑定操作。

/*	weak_table:weak_table_t结构类型的全局的弱引用表。referent_id:weak指针所指的对象。*referrer_id:weak修饰的指针的地址。crashIfDeallocating:如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,id *referrer_id, bool crashIfDeallocating)
{objc_object *referent = (objc_object *)referent_id;objc_object **referrer = (objc_object **)referrer_id;// 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作if (!referent  ||  referent->isTaggedPointer()) return referent_id;// 确保被引用的对象可用(没有在析构,同时应该支持weak引用)bool deallocating;if (!referent->ISA()->hasCustomRR()) {deallocating = referent->rootIsDeallocating();}else {  //不能被weak引用,直接返回nilBOOL (*allowsWeakReference)(objc_object *, SEL) =(BOOL(*)(objc_object *, SEL))object_getMethodImplementation((id)referent,SEL_allowsWeakReference);if ((IMP)allowsWeakReference == _objc_msgForward) {return nil;}deallocating =! (*allowsWeakReference)(referent, SEL_allowsWeakReference);}// 正在析构的对象,不能够被弱引用if (deallocating) {if (crashIfDeallocating) {_objc_fatal("Cannot form weak reference to instance (%p) of ""class %s. It is possible that this object was ""over-released, or is in the process of deallocation.",(void*)referent, object_getClassName((id)referent));} else {return nil;}}// now remember it and where it is being stored// 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中weak_entry_t *entry;if ((entry = weak_entry_for_referent(weak_table, referent))) { // 如果能找到weak_entry,则讲referrer插入到weak_entry中append_referrer(entry, referrer);     // 将referrer插入到weak_entry_t的引用数组中}else { // 如果找不到,就新建一个weak_entry_t new_entry(referent, referrer);weak_grow_maybe(weak_table);weak_entry_insert(weak_table, &new_entry);}// Do not set *referrer. objc_storeWeak() requires that the// value not change.return referent_id;
}
  • 如果referent为nil或referent采用了TaggedPointer计数方式,直接返回,不做任何操作。
  • 如果对象不能被weak引用,直接返回nil。
  • 如果对象正在析构,则抛出异常。
  • 如果对象没有再析构且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry。

Weak的释放

调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

当释放对象时,其基本流程如下:

1、调用objc_release。用于减少对象的引用计数。当对象的引用计数变为 0 时,对象会被释放。
2、因为对象的引用计数为0,所以执行dealloc。dealloc 是对象的析构函数,用于执行对象释放前的清理工作。
3、在dealloc中,调用了_objc_rootDealloc函数。是根析构函数,执行对象的最终释放操作。
4、在_objc_rootDealloc中,调用了object_dispose函数。用于释放对象的内存并进行相关清理操作。
5、调用objc_destructInstance。用于销毁对象实例,包括释放实例变量。
6、最后调用objc_clear_deallocating。用于处理所有指向该对象的弱引用,将它们清零(nil)。

weak指针管理中使用了3个HashTable!!!
其中两个使用oc对象作为index!!!还有一个使用弱引用指针的地址作为index计算方式
除了全局SideTables(), 其他两个的HashTable都使用开放寻址法作为Hash碰撞解决方法!!!

weak修饰的属性,如何自动置为nil的?

Runtime维护了一个Weak表,用于存储指向某个对象的所有Weak指针。 Weak表其实是一个哈希表,Key是所指对象的地址,Value是Weak指针的地址(这个地址的值是所指对象的地址)的数组。 在对象被回收的时候,经过一层层调用,会最终触发(clearDeallocating)方法将所有Weak指针的值设为nil。

weak 的实现原理可以概括为以下三步:

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。


http://www.ppmy.cn/server/115784.html

相关文章

7.统一网关-Gateway

文章目录 1.统一网关介绍2.网关开发3.predicate4.Route Predicate Factories(路由断言工厂)4.1Path 路由断言工厂4.2.Method 路由断言工厂4.3 Header 路由断言工厂4.4 Query 路由断言工厂4.5 Host 路由断言工厂4.6 After 路由断言工厂4.7 Before 路由断言工厂4.8 Between 路由断…

自己看---华为od--敏感字段加密

题目描述 给定一个由多个命令字组成的命令字符串&#xff1a; 字符串长度小于等于127字节&#xff0c;只包含大小写字母&#xff0c;数字&#xff0c;下划线和偶数个双引号&#xff1b; 命令字之间以一个或多个下划线_进行分割&#xff1b; 可以通过两个双引号””来标识包含下…

拓数派荣登2024年《财富》中国最具社会影响力的创业公司

9月11日&#xff0c;全球著名商业杂志《财富》(Fortune Magazine&#xff09;在其中文版发布“2024年中国最具社会影响力的创业公司”榜单。拓数派凭借基础AI理论、产品在核心领域应用&#xff0c;AI向善品牌影响力等方面的综合竞争力荣誉上榜。 作为《财富》最具权威性的榜单…

设计模式-行为型模式-迭代器模式

1.迭代器模式的定义 迭代器模式提供一种对容器对象中的各个元素进行访问的方法&#xff0c;而不需要暴露该对象的内部细节&#xff1b; 在软件系统中&#xff0c;容器对象有两个职责&#xff1a;一是存储数据&#xff0c;二是遍历数据&#xff1b;从依赖性上看&#xff0c;前者…

测试开发基础——软件测试中的bug

二、软件测试中的Bug 1. 软件测试的生命周期 软件测试贯穿于软件的整个生命周期 需求分析 测试计划 测试设计与开发 测试执行 测试评估 上线 运行维护 用户角度&#xff1a;软件需求是否合理 技术角度&#xff1a;技术上是否可行&#xff0c;是否还有优化空间 测试角度…

ARM32开发——DMA

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 基础概念CPURAM外设 ARM32程序存储 执行过程取数据 执行操作流程总结 基础概念 CPU CPU&#xff08;Central Processing Unit&am…

浅谈AI伦理与社会的影响

引言 1.1 AI技术的发展与社会影响 人工智能&#xff08;AI&#xff09;技术自20世纪50年代诞生以来&#xff0c;经历了多次技术革新和应用扩展&#xff0c;如今已成为推动社会进步的重要力量。AI技术的快速发展不仅在科技领域取得了显著成就&#xff0c;也在社会各个层面产生…

Matlab simulink建模与仿真 第十章(模型扩展功能库)

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 一、模型扩展功能库中的模块概览 注&#xff1a;下面不会对Block Support Table模块进行介绍。 二、基于触发的和基于时间的线性化模块 1、Trigger-Based Linearization基于触发的线性化模块 &#xff08;1…