Weak基本用法
Weak
表示弱引用,用weak
修饰,描述的引用对象的计数器并不会增加,并且weak
指针在引用的对象被释放时自动置为nil
,可以解决循环引用问题。
那么weak
的具体实现原理如何呢?
Weak实现原理
iOS是如何实现weak
的呢,其实weak
的底层是一个hash
表,key
是所指向对象的指针,value
是weak
指针的地址数组(因为一个对象可能被多个弱引用指针指向)。
Runtime
维护了一张weak
表,用来存储某个对象的所有weak
指针,
之前探究ARC的时候探究过ARC中weak的底层汇编。
可以看见weak的一整个周期就是从objc_initWeak开始,等到对象释放后,调用objc_destroyWeak销毁指针。
objc_initWeak,objc_destroyWeak
id
objc_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 为 true
if (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 返回 false
FoundBucket = 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下标对应的Bucket
const 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指针,也是一个hash
size_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)
首先可以看见四个参数:
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 viable
bool 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 判断是否触发 crash
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_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,可以联想到,解除也是利用这个。下面看看解除引用的具体实现。
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;
// 在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) {
// 不为空 将标记记录为false
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);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
这段代码的主要流程
- 从
weak_table
中根据找到被引用对象对应的entry
,然后将弱引用变量指针referrer
从entry
中移除。 - 移除弱引用变量指针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;
// 没有直接返回nil
if (!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
。
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 和 referrer
entry
以及比较熟悉了,一个对象的弱引用记录,referrer
则是代表弱引用变量,每次被弱引用时,都会将弱引用变量指针referrer
加入entry
中,而当原对象被释放时,会将entry
清空并移除。
看看entry的定义:
// MARK: - weak_entry_t
struct 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
,最初时该标志位为false
,entry
使用的是一个有序数组inline_referrers
的存储结构。- 当inline_referrers的成员数量超过
WEAK_INLINE_COUNT
,out_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;
}
}
// 找到对应位置后置为nil
entry->referrers[index] = nil;
// entry存的weak元素数量减1
entry->num_refs--;
}
从entry移除referrer的步骤:
dealloc
当被引用的对象被释放后,会去检查isa.weakly_referenced
标志位,每个被弱引用的对象weakly_referenced
标志位都为true。
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 根据指针获取对应 Sidetable
SideTable& 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
。
以上就是weak的实现原理。
原文地址:https://blog.csdn.net/chabuduoxs/article/details/126000448
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_18227.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!