当前位置:网站首页>[OC学习笔记]weak的实现原理
[OC学习笔记]weak的实现原理
2022-08-02 07:51:00 【Billy Miracle】
我们在使用weak时,编译器为我们做了什么呢?
NSObject *object = [NSObject alloc];
id __weak objc = object;
可以看到,首先调用了objc_initWeak
函数。
objc_initWeak
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
方法有两个参数location
和newObj
:
location
:__weak
指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil
newObj
:所引用的对象。即例子中的obj
对于非空的对象,会继续调用storeWeak
函数。
storeWeak
// Template parameters.
enum HaveOld {
DontHaveOld = false, DoHaveOld = true };
enum HaveNew {
DontHaveNew = false, DoHaveNew = true };
// Update a weak variable.
// If HaveOld is true, the variable has an existing value
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
// assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is
// deallocating or newObj's class does not support weak references.
// If CrashIfDeallocating is false, nil is stored instead.
// 更新弱变量。
// 如果 HaveOld 为 true,则该变量具有需要清理的现有值。此值可能为零。
// 如果 HaveNew 为 true,则需要将新值分配到变量中。此值可能为零。
// 如果 CrashIfDeallocating 为 true,则在 newObj 正在解除分配或 newObj 的类不支持弱引用时,该过程将停止。
// 如果 CrashIfDeallocation 为 false,则改为存储 nil。
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
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;
// 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:
if (haveOld) {
// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
// 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
oldTable = nil;
}
if (haveNew) {
// 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
newTable = &SideTables()[newObj];
} else {
// 如果weak ptr不需要引用一个新obj,则newTable = nil
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,防止弱引用机制和 +initialize 机制之间的死锁。
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 如果cls还没有初始化,先初始化,再尝试设置weak
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
// 如果这个类以+initialize完成,那么就没事。
// 如果这个类仍然在这个线程上运行+initialize(即+initialize在自身的实例上调用storeWeak),
// 那么我们可以继续,但它将显示为初始化并且尚未初始化为上面的检查。
// 相反,将先前初始化类设置为在重试时识别它。
previouslyInitializedClass = cls;// 这里记录一下previouslyInitializedClass, 防止改if分支再次进入
goto retry;// 重新获取一遍newObj,这时的newObj应该已经初始化过了
}
}
// Clean up old value, if any.
// 清理旧值(如果有)。
if (haveOld) {
// 如果weak_ptr之前弱引用过别的对象oldObj,
// 则调用weak_unregister_no_lock,
// 在oldObj的weak_entry_t中移除该weak_ptr地址
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
// 分配新值(如果有)。
if (haveNew) {
// 如果weak_ptr需要弱引用新的对象newObj
// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// (2) 更新newObj的isa的weakly_referenced bit标志位
// Set is-weakly-referenced bit in refcount table.
if (!_objc_isTaggedPointerOrNil(newObj)) {
newObj->setWeaklyReferenced_nolock();
}
// (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
// 将weak ptr指向object
}
else {
// No new value. The storage is not changed.
}
// 解锁,其他线程可以访问oldTable, newTable了
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// This must be called without the locks held, as it can invoke
// arbitrary code. In particular, even if _setWeaklyReferenced
// is not implemented, resolveInstanceMethod: may be, and may
// call back into the weak reference machinery.
// 这必须在不持有锁的情况下调用,因为它可以调用任意代码。
// 特别是,即使_setWeaklyReferenced没有实现,
// resolveInstanceMethod:也可能是,并且可能回调到弱参考机制中。
callSetWeaklyReferenced((id)newObj);
return (id)newObj;// 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
}
storeWeak
方法的实现代码非常长,但是并不难以理解。下面我们来分析下。
storeWeak
方法实际上是接收了5个参数,分别是haveOld
、haveNew
和crashIfDeallocating
,这三个参数都是以模板的方式传入的,是三个bool
类型的参数。 分别表示weak
指针之前是否指向了一个弱引用、weak
指针是否需要指向一个新的引用、若被弱引用的对象正在dealloc
,此时再弱引用该对象是否应该crash
。- 该方法维护了
oldTable
和newTable
分别表示旧的引用弱表和新的弱引用表,它们都是SideTable
的hash
表。 - 如果
weak
指针之前指向了一个弱引用,则会调用weak_unregister_no_lock
方法将旧的weak
指针地址移除。 - 如果
weak
指针需要指向一个新的引用,则会调用weak_register_no_lock
方法将新的weak
指针地址添加到弱引用表中。 - 调用
setWeaklyReferenced_nolock
方法修改weak
新引用的对象的bit
标志位。
这个方法中的重点也就是weak_unregister_no_lock
和weak_register_no_lock
这两个方法。而这两个方法都是操作的SideTable
这样一个结构的变量,那么继续探究前需要了解下SideTable
。
SideTable
struct 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 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);
};
SideTable
有三个成员:
spinlock_t slock
: 自旋锁,用于上锁/解锁SideTable
。RefcountMap refcnts
:用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t
的引用计数溢出时才会用到)。weak_table_t weak_table
: 存储对象弱引用指针的hash表。是OC中weak
功能实现的核心数据结构。
weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entries
: hash数组,用来存储弱引用对象的相关信息weak_entry_t
num_entries
: hash数组中的元素个数mask
:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)max_hash_displacement
:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
weak_table_t
是一个典型的hash结构。weak_entries
是一个动态数组,用来存储weak_entry_t
类型的元素,这些元素实际上就是OC对象的弱引用信息。
weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;// 被弱引用的对象
union {
// 引用该对象的对象列表,联合。
// 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers
struct {
weak_referrer_t *referrers;
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];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)// 构造方法,里面初始化了静态数组
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
在weak_entry_t
的结构定义中有联合体,在联合体的内部有定长数组inline_referrers[WEAK_INLINE_COUNT]
和动态数组weak_referrer_t *referrers
两种方式来存储弱引用对象的指针地址。通过out_of_line()
这样一个函数方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT
时,使用定长数组。当超过WEAK_INLINE_COUNT
时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。
弱引用表的结构是一个hash结构的表,Key是所指对象的地址,Value是weak
指针的地址(这个地址的值是所指对象的地址)数组。那么接下来看看这个弱引用表是怎么维护这些数据的。
weak_register_no_lock方法添加弱引用
/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */
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;
// 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
if (_objc_isTaggedPointerOrNil(referent)) return referent_id;
// ensure that the referenced object is viable
// 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
if (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 {
// ReturnNilIfDeallocating
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;
}
这个方法需要传入四个参数:
weak_table
:weak_table_t
结构类型的全局的弱引用表。referent_id
:weak
指针。*referrer_id
:weak
指针地址。deallocatingOptions
:若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash或者置为nil
。
下面简单总结下代码的流程:
- 如果
referent
为nil
或采用了TaggedPointer
计数方式,直接返回,不做任何操作。 - 如果对象不能被
weak
引用,直接返回nil
。 - 如果对象正在析构,则抛出异常或者返回
nil
。 - 如果对象没有在析构且可以被
weak
引用,则调用weak_entry_for_referent
方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry
,如果能够找到则调用append_referrer
方法向其中插入weak
指针地址。否则新建一个weak_entry
,再插入。
weak_entry_for_referent取元素
/** * Return the weak reference table entry for the given referent. * If there is no entry for referent, return NULL. * Performs a lookup. * * @param weak_table * @param referent The object. Must not be nil. * * @return The table of weak referrers to this object. */
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;
// 这里通过 & weak_table->mask的位操作,来确保index不会越界
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); // 触发bad weak table crash
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
// 当hash冲突超过了可能的max hash 冲突时,说明元素没有在hash表中,返回nil
return nil;
}
}
return &weak_table->weak_entries[index];
}
append_referrer添加元素
/** * 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()) {
// 如果weak_entry 尚未使用动态数组,走这里
// Try to insert inline.
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的位置已经存满了,则要转型为referrers,做动态数组。
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
// will fix it and rehash it.
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;
}
// 对于动态数组的附加处理:
ASSERT(entry->out_of_line());// 断言: 此时一定使用的动态数组
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍
return grow_refs_and_insert(entry, new_referrer);// 扩容,并插入
}
// 如果不需要扩容,直接插入到weak_entry中
// 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer
// 这里weak_entry_t 的hash算法和 weak_table_t的hash算法是一样的,同时扩容/减容的算法也是一样的
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
// '& (entry->mask)' 确保了 begin的位置只能大于或等于 数组的长度
size_t index = begin; // 初始的hash index
size_t hash_displacement = 0; // 用于记录hash冲突的次数,也就是hash再位移的次数
while (entry->referrers[index] != nil) {
hash_displacement++;
// index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)
index = (index+1) & entry->mask;
// index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
// 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
entry->max_hash_displacement = hash_displacement;
}
// 将ref存入hash数组,同时,更新元素个数num_refs
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
首先确定是使用定长数组还是动态数组,如果是使用定长数组,则直接将weak
指针地址添加到数组即可,如果定长数组已经用尽,则需要将定长数组中的元素转存到动态数组中。
weak_unregister_no_lock移除引用
如果weak
指针之前指向了一个弱引用,则会调用weak_unregister_no_lock
方法将旧的weak
指针地址移除。
/** * Unregister an already-registered weak reference. * This is used when referrer's storage is about to go away, but referent * isn't dead yet. (Otherwise, zeroing referrer later would be a * bad memory access.) * Does nothing if referent/referrer is not a currently active weak reference. * Does not zero referrer. * * FIXME currently requires old referent value to be passed in (lame) * FIXME unregistration should be automatic if referrer is collected * * @param weak_table The global weak table. * @param referent The object. * @param referrer The weak reference. */
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))) {
// 查找到referent所对应的weak_entry_t
remove_referrer(entry, referrer);// 在referent所对应的weak_entry_t的hash数组中,移除referrer
bool empty = true;
// 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
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_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
- 首先,它会在
weak_table
中找出referent
对应的weak_entry_t
- 在
weak_entry_t
中移除referrer
- 移除元素后,判断此时
weak_entry_t
中是否还有元素 (empty==true?) - 如果此时
weak_entry_t
已经没有元素了,则需要将weak_entry_t
从weak_table
中移除
到这里为止就是对于一个对象做weak
引用时底层做的事情,用weak
引用对象后引用计数并不会加1,当对象释放时,所有weak
引用它的指针又是如何自动设置为nil
的呢?
dealloc
当对象的引用计数为0时,底层会调用_objc_rootDealloc
方法对对象进行释放,而在_objc_rootDealloc
方法里面会调用rootDealloc
方法。如下是rootDealloc
方法的代码实现:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
- 首先判断对象是否是Tagged Pointer,如果是则直接返回。
- 如果对象是采用了优化的
isa
计数方式,且同时满足对象没有被weak
引用!isa.weakly_referenced
、没有关联对象!isa.has_assoc
、没有自定义的C++析构方法!isa.has_cxx_dtor
、没有用到SideTable来引用计数!isa.has_sidetable_rc
则直接快速释放。 - 如果不能满足2中的条件,则会调用
object_dispose
方法。
object_dispose
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
object_dispose
方法很简单,主要是内部调用了objc_destructInstance
方法
/* objc_destructInstance 在不释放内存的情况下销毁实例。 调用C++析构函数。 调用 ARC ivar 清理。 删除关联引用。 返回“obj”。如果 “obj” 为 nil,则不执行任何操作。 */
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
上面这一段代码很清晰,如果有自定义的C++析构方法,则调用C++析构函数。如果有关联对象,则移除关联对象并将其自身从Association Manager
的map中移除。调用clearDeallocating
方法清除对象的相关引用。
clearDeallocating
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
clearDeallocating
中有两个分支,先判断对象是否采用了优化isa
引用计数,如果没有的话则需要清理对象存储在SideTable中的引用计数数据。如果对象采用了优化isa引用计数,则判断是否有使用SideTable的辅助引用计数(isa.has_sidetable_rc
)或者有weak
引用(isa.weakly_referenced
),符合这两种情况中一种的,调用clearDeallocating_slow
方法。
clearDeallocating_slow
// Slow path of clearDeallocating()
// for objects with nonpointer isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];// 在全局的SideTables中,以this指针为key,找到对应的SideTable
table.lock();
if (isa.weakly_referenced) {
// 如果obj被弱引用
weak_clear_no_lock(&table.weak_table, (id)this);// 在SideTable的weak_table中对this进行清理工作
}
if (isa.has_sidetable_rc) {
// 如果采用了SideTable做引用计数
table.refcnts.erase(this);// 在SideTable的引用计数中移除this
}
table.unlock();
}
这里调用了weak_clear_no_lock
来做weak_table
的清理工作。
/** * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
// 找出weak引用referent的weak 指针地址数组以及数组长度
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];// 取出每个weak ptr的地址
if (referrer) {
if (*referrer == referent) {
// ️如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
*referrer = nil;
}
else if (*referrer) {
// 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);// 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}
总结
- weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
- weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
- 对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
边栏推荐
猜你喜欢
MySQL压缩包方式安装,傻瓜式教学
HCIP第七天
RIP综合实验
用户身份标识与账号体系实践
Biotinyl Cystamine | CAS: 128915-82-2 | biotin cysteamine
MySQL事务(transaction) (有这篇就足够了..)
Figure robot software digital twin station oil and gas pipelines, oil and gas transportation control platform
Visual Analysis of DeadLock
ROS file system and related commands
What is NoSQL?Databases for the cloud-scale future
随机推荐
MFC最详细入门教程[转载]
DeadLock的可视化分析
WebForm DropDownList分别绑定年月
数据表格化打印输出
R语言plotly可视化:使用plotly可视化模型预测真阳性率(True positive)TPR和假阳性率(False positive)FPR在不同阈值(threshold)下的曲线
MySQL - locking mechanism
BGP solves routing black hole through MPLS
mysql启动报错The server quit without updating PID file几种解决办法
postgres 水平分表,自动创建分区,按时间分表
redis高阶使用之Redisson分布式锁源码解析
2022-7-31 12点 程序爱生活 恒指底背离中,有1-2周反弹希望
WebGPU 导入[2] - 核心概念与重要机制解读
爬虫 视频爬取工具you-get
【Network】IP, subnet mask
Application and case analysis of CASA model and CENTURY model
Stop mental exhaustion Daily sharing
设置 height: auto 却无法触发 transition 动画的解决方案
PanGu-Coder:函数级的代码生成模型
Elasticserch 自定义字段,用户会频繁的创建和删除字段,怎么设计mapping?
[ansible]playbook结合项目解释执行步骤