肿瘤康复网,内容丰富有趣,生活中的好帮手!
肿瘤康复网 > iOS——weak实现原理

iOS——weak实现原理

时间:2019-03-28 23:17:09

相关推荐

Weak基本用法

Weak表示弱引用,用weak修饰,描述的引用对象的计数器并不会增加,并且weak指针在引用的对象被释放时自动置为nil,可以解决循环引用问题。

那么weak的具体实现原理如何呢?

Weak实现原理

iOS是如何实现weak的呢,其实weak的底层是一个hash表,key是所指向对象的指针,valueweak指针的地址数组(因为一个对象可能被多个弱引用指针指向)。

Runtime维护了一张weak表,用来存储某个对象的所有weak指针,

之前探究ARC的时候探究过ARC中weak的底层汇编。

可以看见weak的一整个周期就是从objc_initWeak开始,等到对象释放后,调用objc_destroyWeak销毁指针。

那么首先就从这个入口 objc_initWeak开始看。

objc_initWeak,objc_destroyWeak

在objc库找一下源码。

idobjc_initWeak(id *location, id newObj){// newObj 是weak对象地址if (!newObj) {*location = nil;return nil;}// 调用storeWeak函数 传入模板参数:haveOld = false, haveNew = true, return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);}

可以看见底层是由storeWeak来实现逻辑的,我们了解一下他的模板。

storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj);

其中DontHaveOld、DoHaveNew和DoCrashIfDeallocating都是模板参数,具体含义如下:

enum HaveOld {DontHaveOld = false, DoHaveOld = true }; // 是否有值enum HaveNew {DontHaveNew = false, DoHaveNew = true }; // 是否有新值enum CrashIfDeallocating {DontCrashIfDeallocating = false, DoCrashIfDeallocating = true}; // 操作正在释放中的对象是否 Crash

static id storeWeak(id *location, objc_object *newObj){// location是weak指针,newObj是weak将要指向的对象assert(haveOld || haveNew);if (!haveNew) assert(newObj == nil);Class previouslyInitializedClass = nil;id oldObj;SideTable *oldTable;SideTable *newTable;// Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:// 根据模板参数的不同执行不同的代码// 从SideTables中取出存储弱引用表SideTable(weak_table_t的一层封装)if (haveOld) {// 获得索引为oldObj存储的值oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}if (haveNew) {// 更改新值指针,获得以newObj为索引存储的值newTable = &SideTables()[newObj];} else {newTable = nil;}// 加锁操作,防止多线程中竞争冲突SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);// 再次判断location是否和oldObj相等// 如果不相等说明当前location已经处理过oldObj,但是被其他线程修改了if (haveOld && *location != oldObj) {// 解锁 再次操作SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;}// Prevent a deadlock between the weak reference machinery// and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa.// 防止弱引用间死锁// 并且通过+initialize初始化构造器保证所有弱引用的isa非空指向if (haveNew && newObj) {// 获取新对象的isa指针Class cls = newObj->getIsa();// 如果该对象类还未进行初始化则进行初始化if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);// 对其isa指针进行初始化_class_initialize(_class_getNonMetaClass(cls, (id)newObj));//如果该类已经完成执行+initialize方法是最理想情况//如果该类+initialize在线程中//例如+initialize正在调用storeWeak方法//需要手动对其增加保护策略,并设置previouslyInitializedClass指针进行标记previouslyInitializedClass = cls;// 再次尝试goto retry;}}// 如果存在旧值,清除旧值对应的弱引用表// Clean up old value, if any.if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// Assign new value, if any.// 如果存在新值,注册新值对应的弱引用表if (haveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);// 如果弱引用被释放的weak_register_no_lock方法返回nil// 设置 isa 标志位 weakly_referenced 为 trueif (newObj && !newObj->isTaggedPointer()) {newObj->setWeaklyReferenced_nolock();}// Do not set *location anywhere else. That would introduce a race.// 之前不要设置location对象, 这里需要更改指针指向*location = (id)newObj;}else {// 没有新值无需操作}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);return (id)newObj;}

具体流程如下所示:

大概做了以下几件事:

1.从全局的哈希表SideTables中,利用对象本身地址进行位运算后得到对应下标,取得该对象的弱引用表。SideTables是一个 64 个元素长度的散列表,发生碰撞时,可能一个SideTable中存在多个对象共享一个弱引用表。

2.如果有分配新值,则检查新值对应的类是否初始化过,如果没有,则就地初始化。

3.如果location有指向其他旧值,则将旧值对应的弱引用表进行注销。

4.如果分配了新值,将新值注册到对应的弱引用表中。将isa.weakly_referenced设置为true,表示该对象是有弱引用变量,释放时要去清空弱引用表。

看到这里可能还是不能理解这一段代码,其中出现了几个新的概念,sideTable

,weak_register_no_lock 和 weak_unregister_no_lock,下面具体解析一下它们的底层原理。

SideTable

为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,虽然名字后面又个"s",但其不过还是一个全局的Hash桶,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。来管理引用计数和weak指针。

之前在探究ARC究竟做了什么的时候,在保存引用计数这里提到过,如果引用计数发生上溢和下溢,会将引用计数存在SideTable类中,今天就来看看这个类内部实现。查看其源码

// MARK: - sideTable类struct SideTable {spinlock_t slock;//保证原子操作的自旋锁RefcountMap refcnts;//保存引用计数的散列表weak_table_t weak_table;//保存weak引用的全局散列表SideTable() {memset(&weak_table, 0, sizeof(weak_table));}~SideTable() {_objc_fatal("Do not delete SideTable.");}void lock() {slock.lock(); }void unlock() {slock.unlock(); }void forceReset() {slock.forceReset(); }// 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);};

首先可以看见有三个成员变量.

spinlock_t slock

这个成员变量是一个自旋锁,保证同一时间内只有一个线程访问共享资源,如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。这里主要用来保证操作引用计数不会造成线程竞争而产生错误。

RefcountMap

这个成员变量就是保存引用计数的散列表。看一下下面这张图。

结合它的定义:typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

可以看出来这个散列表的key是对象的地址,value是引用计数。

当value为0时,RefcountMap会自动清除这条记录。

每个RefcountMap中存储一堆对象的引用计数。

那么查询引用计数的流程是什么呢?

通过计算对象地址的哈希值, 来从 SideTables 中获取对应的 SideTable. 哈希值重复的对象的引用计数存储在同一个 SideTable 里.SideTable 使用 find() 方法和重载 [] 运算符的方式, 通过对象地址来确定对象对应的桶. 最终执行到的查找算法是 LookupBucketFor()。这个涉及到了OC底层的存储,之前在学习关联对象的时候,遇见过这个函数,不过关联对象偏向于使用InsertIntoBucket完成工作。 下面分析分析源码。

LookupBucketFor()

value_type& FindAndConstruct(const KeyT &Key) {BucketT *TheBucket;if (LookupBucketFor(Key, TheBucket))return *TheBucket;return *InsertIntoBucket(Key, ValueT(), TheBucket);}// 找到了就返回这个对象的桶// 没有找到则进行插入操作。// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in/// FoundBucket. If the bucket contains the key and a value, this returns/// true, otherwise it returns a bucket with an empty marker or tombstone and/// returns false.// 为Val查找相应的桶,在/// FoundBucket中返回。如果桶中包含键和值,则返回/// true,否则返回带有空标记或墓碑的桶,并返回false。template<typename LookupKeyT>bool LookupBucketFor(const LookupKeyT &Val,const BucketT *&FoundBucket) const {// 获取buckets的首地址const BucketT *BucketsPtr = getBuckets();// 获取可存储的buckets的总数const unsigned NumBuckets = getNumBuckets();if (NumBuckets == 0) {// 如果NumBuckets = 0 返回 falseFoundBucket = nullptr;return false;}// FoundTombstone - Keep track of whether we find a tombstone while probing.const BucketT *FoundTombstone = nullptr;const KeyT EmptyKey = getEmptyKey();const KeyT TombstoneKey = getTombstoneKey();assert(!KeyInfoT::isEqual(Val, EmptyKey) &&!KeyInfoT::isEqual(Val, TombstoneKey) &&"Empty/Tombstone value shouldn't be inserted into map!");// 计算hash下标unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);unsigned ProbeAmt = 1;while (true) {// 内存平移:找到hash下标对应的Bucketconst BucketT *ThisBucket = BucketsPtr + BucketNo;// Found Val's bucket? If so, return it.if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {// 如果查询到`Bucket`的`key`和`Val`相等 返回当前的Bucket说明查询到了FoundBucket = ThisBucket;return true;}// If we found an empty bucket, the key doesn't exist in the set.// Insert it and return the default value.// 如果bucket为空桶,表示可以插入if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {// 如果曾遇到墓碑, 则使用墓碑的位置// of the empty bucket we eventually probed to.FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;return false;//找到空占位符, 则表明表中没有已经插入了该对象的桶}// If this is a tombstone, remember it. If Val ends up not in the map, we// prefer to return it than something that would require more probing.// Ditto for zero values.//如果找到了墓碑if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&!FoundTombstone)FoundTombstone = ThisBucket; //记录墓碑的位置if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)//这里涉及到最初定义 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap, 传入的第三个参数 true//这个参数代表是否可以清除 0 值, 也就是说这个参数为 true 并且没有墓碑的时候, 会记录下找到的 value 为 0 的桶FoundTombstone = ThisBucket;// Otherwise, it's a hash collision or a tombstone, continue quadratic//用于计数的 ProbeAmt 如果大于了数组容量, 就会抛出异常if (ProbeAmt > NumBuckets) {FatalCorruptHashTables(BucketsPtr, NumBuckets);}// 重新计算hash下标BucketNo += ProbeAmt++;BucketNo &= (NumBuckets-1);}}

查找算法会先对桶的个数进行判断, 如果桶数为 0 则 return false 回上一级调用插入方法. 如果查找算法找到空桶或者墓碑桶, 同样 return false 回上一级调用插入算法, 不过会先记录下找到的桶. 如果找到了对象对应的桶, 只需要对其引用计数 + 1 或者 - 1. 如果引用计数为 0 需要销毁对象, 就将这个桶中的 key 设置为 TombstoneKey,这样可以避免后续给hash相同的对象增加引用计数时,在销毁后的位置插入了桶。

weak_table_t weak_table

储存对象弱引用指针的hash表。weak功能实现的核心数据结构。

struct weak_table_t {weak_entry_t *weak_entries; //连续地址空间的头指针, 数组//管理所有指向某对象的weak指针,也是一个hashsize_t num_entries; //数组中已占用位置的个数uintptr_t mask; //数组下标最大值(即数组大小 -1)uintptr_t max_hash_displacement; //最大哈希偏移值};

weak中解除和链接弱引用的实现

首先在之前initWeak里面提到了weak_register_no_lock,这个犯法是用来链接弱引用表的,指向对象注册在弱引用表中。

看看源码:

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating)

首先可以看见四个参数:

@param weak_table 一个全局的weak表@param referent 弱引用指向的对象@param referrer 弱指针地址标记最后一个对象是否正在被释放时造成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;// 弱引用变量if (!referent || referent->isTaggedPointer()) return referent_id;// 确保弱引用对象是否可行// ensure that the referenced object is viablebool deallocating;if (!referent->ISA()->hasCustomRR()) {deallocating = referent->rootIsDeallocating();}else {BOOL (*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);}// 如果正在释放中,则根据 crashIfDeallocating 判断是否触发 crashif (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_entry_t *entry;// 如果当前表中有该对象的记录则直接加入该 weak 表中对应记录if ((entry = weak_entry_for_referent(weak_table, referent))) {append_referrer(entry, referrer);} else {// 没有在 weak 表中找到对应记录,则新建一个记录weak_entry_t new_entry(referent, referrer);// 查看是否需要扩容weak_grow_maybe(weak_table);// 将记录插入 weak 表中weak_entry_insert(weak_table, &new_entry);}// Do not set *referrer. objc_storeWeak() requires that the // value not change.return referent_id;}

上述代码主要功能如下:

判断被指向对象是否可行,也就是判断其是否正在释放,并且会根据crashIfDeallocating判断是否触发crash。在weak_table中检测是否有被指向对象的entry,如果有的话,直接将该弱引用变量指针加入到该entry中如果没有找到对应的entry,新建一个entry,并将弱引用变量指针地址加入entry,同时检查weaktable是否扩容。

从上可以看出链接弱引用主要利用到了entry,可以联想到,解除也是利用这个。下面看看解除引用的具体实现。

voidweak_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;// 在weak表中查找if ((entry = weak_entry_for_referent(weak_table, referent))) {// 找到相应记录后,将该引用从记录中移除。remove_referrer(entry, referrer);// 移除后检查该记录是否为空bool empty = true;if (entry->out_of_line() && entry->num_refs != 0) {// 不为空 将标记记录为falseempty = 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);}}// Do not set *referrer = nil. objc_storeWeak() requires that the // value not change.}

这段代码的主要流程

weak_table中根据找到被引用对象对应的entry,然后将弱引用变量指针referrerentry中移除。移除弱引用变量指针referrer之后,检查entry是否为空,如果为空将其从weak_table中移除

weak_table

上面提到了一个全局表weak_table,用来保存每一个对象的entry,下面看看具体实现。

static weak_entry_t *weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent){assert(referent);// 获取weak_table中存储所有对象entry的数组weak_entry_t *weak_entries = weak_table->weak_entries;// 没有直接返回nilif (!weak_entries) return nil;// hash_pointer 对地址做位运算得出哈希表下标的方式size_t begin = hash_pointer(referent) & weak_table->mask;size_t index = begin;size_t hash_displacement = 0;// 遍历weak_table中的weak_entries,比对weak_entries[index].referent对象和referent对象是否相等while (weak_table->weak_entries[index].referent != referent) {index = (index+1) & weak_table->mask;// 不能超过 weak_table 最大长度限制// 回到初始下标,异常报错if (index == begin) bad_weak_table(weak_table->weak_entries);hash_displacement++;// 以及达到最大hash值,说明遍历完毕没找到if (hash_displacement > weak_table->max_hash_displacement) {return nil;}}// 返回weak指针return &weak_table->weak_entries[index];}

上面代码的流程:

通过被引用对象地址计算获得哈希表下标。检查对应下标存储的是不是我们要找到地址,如果是则返回该地址。如果不是则继续往下找(线性查找),直至找到。在下移的过程中,下标不能超过weak_table最大长度,同时hash_displacement不能超过记录的max_hash_displacement最大哈希位移。max_hash_displacement是所有插入操作时记录的最大哈希位移,如果超过了,抛出异常。

下面看看如何插入entry

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry){weak_entry_t *weak_entries = weak_table->weak_entries;assert(weak_entries != nil);// 通过哈希算法得到下标size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);size_t index = begin;size_t hash_displacement = 0;// 判断当前下标是否为空,如果不是继续往下寻址空位while (weak_entries[index].referent != nil) {index = (index+1) & weak_table->mask;if (index == begin) bad_weak_table(weak_entries);hash_displacement++;}// 找到空位后存入weak_entries[index] = *new_entry;weak_table->num_entries++;// 更新最大哈希位移值if (hash_displacement > weak_table->max_hash_displacement) {weak_table->max_hash_displacement = hash_displacement;}}

看来之前的过程就很容易理解这个插入了,首先根据对象地址计算得到hash值,判断该位置是否为空,不为空往下找,直到找到空位置,将弱引用变量指针存入空位,同时更新weak_table的当前成员数量num_entries和最大哈希位移max_hash_displacement

下面看看移除entry的代码:

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry){// 释放 entry 中的所有弱引用if (entry->out_of_line()) free(entry->referrers);// 置空指针bzero(entry, sizeof(*entry));// 更新 weak_table 对象数量,并检查是否可以缩减表容量weak_table->num_entries--;weak_compact_maybe(weak_table);}

释放entry和其中的弱引用变量。更新 weak_table 对象数量,并检查是否可以缩减表容量

entry 和 referrer

entry以及比较熟悉了,一个对象的弱引用记录,referrer则是代表弱引用变量,每次被弱引用时,都会将弱引用变量指针referrer加入entry中,而当原对象被释放时,会将entry清空并移除。

看看entry的定义:

// MARK: - weak_entry_tstruct weak_entry_t {DisguisedPtr<objc_object> referent;// 对象的地址union {struct {weak_referrer_t *referrers;//可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。//指向 referent 对象的 weak 指针数组uintptr_t out_of_line_ness : 2;// 这里标记是否超过内连边界uintptr_t num_refs : PTR_MINUS_2;// 数组已占大小uintptr_t mask;// 数组大小uintptr_t max_hash_displacement;// 最大hash偏移值};struct {// out_of_line_ness field is low bits of inline_referrers[1]weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //只有4个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。//当指向这个对象的weak指针不超过4个,则直接使用数组inline_referrers};};

可以看见苹果这里为了避免数量少使用hash造成性能浪费,使用共用体,做了一个判断,如果weak指针数量小于4就使用inline_referrers[WEAK_INLINE_COUNT]

下面看看entry是如何添加referrer的:

// MARK: - 将weak指针添加到entry的weak指针集合中/** * Add the given referrer to set of weak pointers in this entry.* Does not perform duplicate checking (b/c weak pointers are never* added to a set twice). ** @param entry The entry holding the set of weak pointers. * @param new_referrer The new weak pointer to be added.*/static void append_referrer(weak_entry_t *entry, objc_object **new_referrer){if (! entry->out_of_line()) {// 没有超出范围,直接加入到对应位置中for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {if (entry->inline_referrers[i] == nil) {entry->inline_referrers[i] = new_referrer;return;}}// Couldn't insert inline. Allocate out of line.// // 如果 inline_referrers 超出 WEAK_INLINE_COUNT 数量,则执行下面代码// 开辟新空间weak_referrer_t *new_referrers = (weak_referrer_t *)calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));// This constructed table is invalid, but grow_refs_and_insert// 将原来的引用转移到新空间for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {new_referrers[i] = entry->inline_referrers[i];}// 修改 entry 内容及标志位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;}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;// 该下表位置下不为空,发生 hash 冲突,while (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++;}

entry的结构和weak_table相似,都使用了哈希表,并且使用线性探测法寻找对应位置。在此基础上有一点不同的地方:

entry有一个标志位out_of_line,最初时该标志位为falseentry使用的是一个有序数组inline_referrers的存储结构。当inline_referrers的成员数量超过WEAK_INLINE_COUNTout_of_line标志位变成true,开始使用哈希表存储结构。每当哈希表负载超过 3/4 时会进行扩容。

下面看看如何去掉weak指针:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer){if (! entry->out_of_line()) {// 未超出 inline_referrers 时直接将对应位置清空for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {if (entry->inline_referrers[i] == old_referrer) {entry->inline_referrers[i] = nil;return;}}_objc_inform("Attempted to unregister unknown __weak variable ""at %p. This is probably incorrect use of ""objc_storeWeak() and objc_loadWeak(). ""Break on objc_weak_error to debug.\n", old_referrer);objc_weak_error();return;}// 超出 inline_referrers 的逻辑,也就是weak指针大于4 // 计算下标size_t begin = w_hash_pointer(old_referrer) & (entry->mask);size_t index = begin;size_t hash_displacement = 0;// 发生哈希冲突继续往后查找while (entry->referrers[index] != old_referrer) {index = (index+1) & entry->mask;if (index == begin) bad_weak_table(entry);hash_displacement++;// 越界抛出异常if (hash_displacement > entry->max_hash_displacement) {_objc_inform("Attempted to unregister unknown __weak variable ""at %p. This is probably incorrect use of ""objc_storeWeak() and objc_loadWeak(). ""Break on objc_weak_error to debug.\n", old_referrer);objc_weak_error();return;}}// 找到对应位置后置为nilentry->referrers[index] = nil;// entry存的weak元素数量减1entry->num_refs--;}

从entry移除referrer的步骤:

out_of_line为false时,从有序数组inline_referrers中查找并移除。out_of_line为true时,从哈希表中查找并移除

dealloc

当被引用的对象被释放后,会去检查isa.weakly_referenced标志位,每个被弱引用的对象weakly_referenced标志位都为true。

NEVER_INLINE voidobjc_object::clearDeallocating_slow(){assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));// 根据指针获取对应 SidetableSideTable& table = SideTables()[this];table.lock();if (isa.weakly_referenced) {// 存在弱引用weak_clear_no_lock(&table.weak_table, (id)this);}if (isa.has_sidetable_rc) {table.refcnts.erase(this);}table.unlock();}

从上面的代码可以看出,在对象释执行dealloc函数时,会检查isa.weakly_referenced标志位,然后判断是否要清理weak_table中的entry

这最后还是走到了前面的remove。

以上就是weak的实现原理。

如果觉得《iOS——weak实现原理》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。