Objective-C中weak实现原理

news/2024/11/30 9:44:00/

Objective-C的对象采用引用计数来管理内存,如果对象被强持有,这个对象的引用计数会增加,如果对象被弱持有,这个对象的引用计数不会增加。弱持有也就是weak如何实现的呢?首先看下weak相关底层实现用到的数据结构,主要分为SideTable,weak_table_t和weak_entry_t这几个数据结构。

struct SideTable {spinlock_t slock;   //锁RefcountMap refcnts;  //引用计数表weak_table_t weak_table;  //weak引用表
};  

SideTable是Objective-C中的引用计数表,它持有了weak引用表,也就是weak_table_t类型的weak_table。

struct weak_table_t {weak_entry_t *weak_entries;size_t    num_entries;  //弱引用项数量uintptr_t mask;         //用于计算哈希的maskuintptr_t max_hash_displacement;  //允许的哈希未命中的次数
};

在weak_table_t中,持有了weak_entry_t指针,这个指针指向一个数组,数组中每个weak_entry_t代表着一个弱引用项,这个数组模拟实现了hash。

struct weak_entry_t {DisguisedPtr<objc_object> referent;union {struct {weak_referrer_t *referrers;     //weak指针数组uintptr_t        out_of_line_ness : 2;uintptr_t        num_refs : PTR_MINUS_2;uintptr_t        mask;uintptr_t        max_hash_displacement;};struct {// out_of_line_ness field is low bits of inline_referrers[1]weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];};};
};

一个弱引用项持有了referent,也就是被引用的对象,接下来使用联合体union持有了weak_referrer_t类型的数组,当weak指针指向的数组不超过4个元素时,使用定长的数组,超过4个元素的时候,使用变长数组。

这里weak_entry_t并没有直接持有被引用的对象,而是持有了DisguisedPtr类型的对象。

template <typename T>
class DisguisedPtr {uintptr_t value;static uintptr_t disguise(T* ptr) {return -(uintptr_t)ptr;}static T* undisguise(uintptr_t val) {return (T*)-val;}public:DisguisedPtr() { }DisguisedPtr(T* ptr) : value(disguise(ptr)) { }DisguisedPtr(const DisguisedPtr<T>& ptr) : value(ptr.value) { }
};

DisguisedPtr的核心方法是disguise和undisguise,disguise所做的事情是把指针的值加了负号,undisguise再次添加负号得到原始数据。做这一层包装替换的原因是防止leaks工具把对象检测为内存泄露。

整体的数据结构大致就是这样,SideTable持有weak_table_t类型的指针,weak_table_t持有

weak_entry_t类型的指针。weak_table_t代表的是weak表,里面存放很多weak指针和weak指针指向的对象,weak_entry_t代表的weak引用项,它持有了DisguisedPtr处理过的对象的指针和weak指针。weak_entry_t在底层是使用数组存储起来的,为了加快访问查找速度,在数组的数据结构上,模拟实现了hash表。

了解了weak引用相关的数据结构之后,可以看下weak实现的过程。当我们使用weak指针,使weak指针指向一个对象后,会执行到objc_storeWeak方法。

id objc_storeWeak(id *location, id newObj)
{return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object *)newObj);
}
template <HaveOld haveOld, HaveNew haveNew,enum CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{ASSERT(haveOld  ||  haveNew);if (!haveNew) ASSERT(newObj == nil);Class previouslyInitializedClass = nil;id oldObj;SideTable *oldTable;SideTable *newTable;retry:if (haveOld) {oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}if (haveNew) {newTable = &SideTables()[newObj];} else {newTable = nil;}SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);if (haveOld  &&  *location != oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;}if (haveNew  &&  newObj) {Class cls = newObj->getIsa();if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//如果class还没有初始化,先进行初始化class_initialize(cls, (id)newObj);previouslyInitializedClass = cls;goto retry;}}// 如果weak指针有旧值,先清理旧值if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// 如果有新值,处理新值if (haveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);// 设置weak引用标记位if (!_objc_isTaggedPointerOrNil(newObj)) {newObj->setWeaklyReferenced_nolock();}//把新值复制给*location,weak指针指向newObj*location = (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);callSetWeaklyReferenced((id)newObj);return (id)newObj;
}

objc_storeWeak函数调用了storeWeak函数,storeWeak函数中的核心方法是weak_register_no_lock。

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{objc_object *referent = (objc_object *)referent_id;objc_object **referrer = (objc_object **)referrer_id;//如果是taggedPointer,直接返回referent_idif (_objc_isTaggedPointerOrNil(referent)) return referent_id;// 查看引用的对象是否在释放,如果在释放,返回nil或者crashif (deallocatingOptions == ReturnNilIfDeallocating ||deallocatingOptions == CrashIfDeallocating) {bool deallocating;if (!referent->ISA()->hasCustomRR()) {deallocating = referent->rootIsDeallocating();}else {// Use lookUpImpOrForward so we can avoid the assert in// class_getInstanceMethod, since we intentionally make this// callout with the lock held.auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),referent->getIsa());if ((IMP)allowsWeakReference == _objc_msgForward) {return nil;}deallocating =! (*allowsWeakReference)(referent, @selector(allowsWeakReference));}if (deallocating) {if (deallocatingOptions == 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;}}}// 查找weak_entry_t,并且追加weak指针weak_entry_t *entry;if ((entry = weak_entry_for_referent(weak_table, referent))) {append_referrer(entry, referrer);} 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;
}

weak_register_no_lock函数的核心在于通过weak_entry_for_referent查找弱引用项,如果已经有了对应的weak_entry_t,直接插入weak指针到数据里面。如果没有则新建new_entry对象,并且加入到weak_entry_t指针指向的数组里面。

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{ASSERT(referent);weak_entry_t *weak_entries = weak_table->weak_entries;if (!weak_entries) return nil;size_t begin = hash_pointer(referent) & weak_table->mask;size_t index = begin;size_t hash_displacement = 0;while (weak_table->weak_entries[index].referent != referent) {index = (index+1) & weak_table->mask;if (index == begin) bad_weak_table(weak_table->weak_entries);hash_displacement++;if (hash_displacement > weak_table->max_hash_displacement) {return nil;}}return &weak_table->weak_entries[index];
}

weak_entry_for_referent的实现比较简单,从weak_table->weak_entries得到weak_entry_t指针,通过

hash_pointer(referent) & weak_table->mask得到哈希的索引,如果有哈希冲突,可以增加索引值,继续查找。可以看出weak_entries实际上是数组的数据结构,为了加快查找速度,在数组上实现了hash。

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{ if (! entry->out_of_line()) {//weak指针数量不超过4个for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {if (entry->inline_referrers[i] == nil) {entry->inline_referrers[i] = new_referrer;return;}}//weak指针数量超过4个,开辟内存存储weak指针weak_referrer_t *new_referrers = (weak_referrer_t *)calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {new_referrers[i] = entry->inline_referrers[i];}entry->referrers = new_referrers;entry->num_refs = WEAK_INLINE_COUNT;entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;entry->mask = WEAK_INLINE_COUNT-1;entry->max_hash_displacement = 0;}//weak指针数量超过4个ASSERT(entry->out_of_line());//数组扩容if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {return grow_refs_and_insert(entry, new_referrer);}size_t begin = w_hash_pointer(new_referrer) & (entry->mask);size_t index = begin;size_t hash_displacement = 0;//找到一个为nil的位置,然后插入new_referrerwhile (entry->referrers[index] != nil) {hash_displacement++;index = (index+1) & entry->mask;if (index == begin) bad_weak_table(entry);}if (hash_displacement > entry->max_hash_displacement) {entry->max_hash_displacement = hash_displacement;}weak_referrer_t &ref = entry->referrers[index];ref = new_referrer;entry->num_refs++;
}

append_referrer函数用于处理weak_entry_t内部的weak指针,这里也同样使用了数组模拟hash,构建了hash表存储weak指针。如果一个对象的weak指针数量不超过4个, 直接使用定长数组插入。如果超过4个,找到为nil的位置并且传入new_referrer。

以上内容是weak指针添加的过程,还有一个weak指针移除的方法。

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id)
{objc_object *referent = (objc_object *)referent_id;objc_object **referrer = (objc_object **)referrer_id;weak_entry_t *entry;if (!referent) return;if ((entry = weak_entry_for_referent(weak_table, referent))) {//移除referrer指针remove_referrer(entry, referrer);//如果没有weak指针指向这个对象,需要移除weak_entry_tbool empty = true;if (entry->out_of_line()  &&  entry->num_refs != 0) {empty = false;}else {for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {if (entry->inline_referrers[i]) {empty = false; break;}}}if (empty) {weak_entry_remove(weak_table, entry);}}
}

weak_unregister_no_lock用于移除一个weak指针,它首先查找对象对应的weak_entry_t,然后找到weak指针哈希表,移除这个weak指针,移除后要判断对象的weak指针是不是已经被清空了,如果被清空了,对应的weak_entry_t需要移除。


http://www.ppmy.cn/news/1505.html

相关文章

使用xlsxwriter简单的将截图插入excel表格中

1.xlsxwriter插入图片 原因&#xff1a; 有个小项目需要测出数据&#xff0c;然后把仪表上截图给插入excel中。 在网上查了一下&#xff0c;发现用xlsxwriter插入图片挺方便的。 import xlsxwriterduang xlsxwriter.Workbook("data.xlsx") sheet duang .add_work…

用Hopper修改代理软件端口

背景 用代理软件可以访问google&#xff0c;但是端口经常不固定&#xff0c;从缺省1080变成了随机。 前几天其实已经用Hopper 3.0看了一次&#xff0c;但是好像不支持go&#xff0c;所以没反编译成功&#xff0c;这次换了4.0&#xff0c;支持了go。 Hopper与逆向 逆向的目的…

数据结构和算法之如何建立图

小白BG.1 邻接矩阵表示的图结点的结构 typedef struct GNode *PtrToGNode;//PtrToGNode是指向GNode的一个指针 struct GNode{ int Nv;//顶点数 int Ne;//边数 WeightType G[MaxVertexNum][MaxVertexNum]; DataType Data[MaxVertexNum];//存顶点的数据 }; typedef PtrToGNode MG…

“剧情+综艺” 助推国潮文化破圈

一舞千年&#xff0c;重现大唐辉煌&#xff1b;一曲流光&#xff0c;雕琢岁月模样&#xff1b;一纸云烟&#xff0c;漫卷诗书山河&#xff1b;跨历史长河&#xff0c;览盛世华章。自从河南卫视开启“剧情综艺”的晚会形式&#xff0c;晚会便多了一种呈现方式。 从2021年《唐宫夜…

基于JSP网上书城的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

一篇文章了解MySQL的group by

准备工作&#xff01; 1.本文章MySQL使用的是5.7&#xff0c;引擎使用的是innodb 2. 使用的表结构&#xff08;t1&#xff09;&#xff0c;字段a上有一个索引&#xff0c; 1. group by常用方法&#xff1a; group by的常规用法是配合聚合函数&#xff0c;利用分组信息进行统…

制作一个谷歌浏览器插件,实现网页数据爬虫

一、什么是浏览器插件 浏览器插件&#xff0c;基于浏览器的原有功能&#xff0c;另外增加新功能的工具&#xff0c;是可定制浏览体验的小型软件程序&#xff0c;让用户可以根据个人需要或偏好来定制浏览器。 如拦截网页中的广告、划词翻译、倍速视频等等。 Chrome、edge等浏…

Allegro如何缩放数据操作指导

Allegro如何缩放数据操作指导 Allegeo上可以缩放数据,尤其是在做结构时候非常有用,具体操作如下 以下图为例,需要把这个数据缩小0.5倍 点击Create Detail命令 Option里面选定一个层面,比如放在Board Geomertry,silkscreen top层 Scaling Factor输入0.5 Find选择所有 …