-
2022-02-22 15:45:42
NSObject 底层原理分析 SideTable
上一篇NSObject 底层原理分析(一)中我们讨论了关于[[NSObject alloc] init],[NSObject new]以及[NSObject dealloc]的执行流程。其中,我们有提到释放NSObject中会判断当前的isa是否经过SideTable优化。因此,这篇文章,我们将研究SideTable到底是什么?!
SlideTable 结构
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 自旋锁slock;
- RefcountMap refcnts 强引用相关;
- weak_table_t weak_table 弱引用相关;
Side Table初始化与析构函数
SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); }
Side Table 提供方法
void lock() { slock.lock(); } // 调用自旋锁方法 void unlock() { slock.unlock(); } // 解除自旋锁方法 void forceReset() { slock.forceReset(); } // 锁重制方法 template<HaveOld, HaveNew> static void lockTwo(SideTable *lock1, SideTable *lock2); template<HaveOld, HaveNew> static void unlockTwo(SideTable *lock1, SideTable *lock2);
自旋锁原理
它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。SlideTable 调用
SlideTable 引用计数+1
当对象调用[obj retain]时候,实际上会出发Objc4中objc_object::rootRetain调用,表明当前引用计数+1,我们来看一下在这个方法中到底做了什么操作。
if (isTaggedPointer()) return (id)this;
首先,判断当前obj对象是否是Tagged Pointer标记过的对象(上一篇我们知道苹果针对64bit设备提供了直接存储小对象的Tagged Pointer技术),当前对象被Tagged Pointer Mask过后则直接放回当前对象即可。
bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do{ } while(slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
没有被Tagged Pointer标记过的对象则会继续执行上面这些代码,初始化Side Table部分信息。StoreExclusive()函数封装了&isa.bits 和 oldisa.bits 进行原子比较字节逐位相等的话,则把 newisa.bits 复制这一部分逻辑。
return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (rawISA()->isMetaClass()) return (id)this; if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // don't check newisa.fast_rr; we already called any RR overrides if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
do-while循环中执行的LoadExclusive(&isa.bits)读取isa的bits信息,并赋值给了oldisa。判断当前newisa没有被Tagged Pointer标记过,则执行以下代码。
ClearExclusive(&isa.bits); if (rawISA()->isMetaClass()) return (id)this; if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain();
- 当前对象是元对象则直接返回obj;
- 当前对象没有tryRetain引用并且被sidetable的自旋锁锁住则执行自旋锁解锁操作;
- 当前对象tryRetain则sidetable进行引用;
这里我们重点关注Side Table Retain做的操作:
id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; }
- Slide Table强引用当前obj对象,执行引用计数+1操作refcntStorage += SIDE_TABLE_RC_ONE;
if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { ASSERT(isa.nonpointer); SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; // isa-side bits should not be set here ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; uintptr_t carry; size_t newRefcnt = addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); if (carry) { refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK); return true; } else { refcntStorage = newRefcnt; return false; } }
- 根据this,从Side Tables中取出Side Table;
- 获取 SideTable 的 refcnts,这个成员变量是一个 Map;
- 存储旧的引用计数器;
- 进行 add 计算,并记录是否有溢出;
- 根据是否溢出计算并记录结果,最后返回;
SideTable 引用技术-1
当我们调用[obj release]操作时候,则会调用以下方法
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc = true, bool handleUnderflow = false)
rootRelease核心方法调用了sidetable_release()方法,与retain调用相同,进行逐个字节判断进行循环。
uintptr_t objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; bool do_dealloc = false; table.lock(); auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING); auto &refcnt = it.first->second; if (it.second) { do_dealloc = true; } else if (refcnt < SIDE_TABLE_DEALLOCATING) { // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it. do_dealloc = true; refcnt |= SIDE_TABLE_DEALLOCATING; } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { refcnt -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return do_dealloc; }
sidetable_release()判断释放条件,当引用计数<SIDE_TABLE_DEALLOCATING时,进行释放dealloc操作。否则,引用计数-1。
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
最终,会触发调用dealloc方法。更多相关内容 -
SideTable结构
2020-11-01 22:00:08下面我们通过分析SideTable的结构来进一步了解内存管理的弱引用存储细节。 结构 在runtime中,有四个数据结构非常重要,分别是SideTables,SideTable,weak_table_t和weak_entry_t。它们和对象的引用计数,以及weak...iOS开发者都知道,当一个对象被释放时,所有对这个对象弱引用的指针都会释放并置为nil,那么系统是如何存储这些弱引用对象的呢?又是如何在一个对象释放时,将这些指向即将释放对象的弱引用的指针置为nil的呢?下面我们通过分析
SideTable
的结构来进一步了解内存管理的弱引用存储细节。结构
在runtime中,有四个数据结构非常重要,分别是
SideTables
,SideTable
,weak_table_t
和weak_entry_t
。它们和对象的引用计数,以及weak引用相关。SideTables
下面我们看下SideTables的结构:
static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); }
reinterpret_cast
,是C++里的强制类型转换符,我们看下SideTableBuf
的定义。上面代码,我们看到StripedMap实际上返回的是一个SideTableBuf对象,那么我们来看下SideTableBuf对象://alignas 字节对齐 // SideTableBuf 静态全局变量 // sizeof(StripedMap<SideTable>) = 4096 //alignas (StripedMap<SideTable>) 是字节对齐的意思,表示让数组中每一个元素的起始位置对齐到4096的倍数 // 因此下面这句话可以翻译为 static uint8_t SideTableBuf[4096] alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)];
SideTableBuf
是一个外部不可见的静态内存区块,存储StripedMap<SideTable>
对象。它是内存管理的基础。我们接下来在看下
StripedMap
的结构enum { CacheLineSize = 64 }; // StripedMap<T> 是一个模板类,根据传递的实际参数决定其中 array 成员存储的元素类型 // 能通过对象的地址,运算出 Hash 值,通过该 hash 值找到对应的 value template<typename T> class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif // PaddedT 为一个结构体 struct PaddedT { T value alignas(CacheLineSize); }; // array 中存放着8个sidetable PaddedT array[StripeCount]; //取得p的哈希值,p就是实例对象的地址 static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast<uintptr_t>(p); // 这里根据对象的地址经过左移和异或操作 最终结果 模 8 得到一个0-7的值 // 即对应该地址对应array中下标的sidetable中 return ((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: // 重写了[]方法 即通过下标获取数组中对应下标的值 // array[index] = array[indexForPointer(p)].value T& operator[] (const void *p) { return array[indexForPointer(p)].value; } const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; } };
StripedMap
是一个以void *
为hash key, T为vaule的hash 表。StripedMap
的所有T类型数据都被封装到array中。综上我们得出
SideTables
的机构实际是下图所示:
SideTable
下面来看下
sideTable
的结构struct SideTable { // 保证原子操作的自旋锁 spinlock_t slock; // 引用计数的 hash 表 RefcountMap refcnts; // weak 引用全局 hash 表 weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); } };
上面是我们简化后的
SideTable
结构体,包含了:- 保证原子属性的自旋锁
spinlock_t
- 记录引用计数值的
RefcountMap
- 用于存储对象弱引用的哈希表
weak_table_t
自旋锁(slock)我们这里就不做过多介绍了,我们先来看下RefcountMap,看下
RefcountMap
结构// RefcountMap 是一个模板类 // key,DisguisedPtr<objc_object>类型 // value,size_t类型 // 是否清除为vlaue==0的数据,true typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
DenseMap是llvm库中的类,是一个简单的二次探测哈希表,擅长支持小的键和值。
RefcountMap
是一个hash map,其key是obj的DisguisedPtr<objc_object>
,而value,则是obj对象的引用计数
,同时,这个map还有个加强版功能,当引用计数为0时,会自动将对象数据清除。上面我们知道了,refcnts是用来存放引用计数的,那么我们如何获取一个对象的引用计数呢?
// 获取一个对象的retainCount inline uintptr_t objc_object::rootRetainCount() { //优化指针 直接返回 if (isTaggedPointer()) return (uintptr_t)this; //没优化则 到SideTable 读取 sidetable_lock(); //isa指针 isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits);//啥都没做 if (bits.nonpointer) {//优化过 isa 指针 uintptr_t rc = 1 + bits.extra_rc;//计数数量 if (bits.has_sidetable_rc) { //bits.has_sidetable_rc标志位为1 表明有存放在sidetable中的引用计数 //读取table的值 相加 rc += sidetable_getExtraRC_nolock(); } //解锁 sidetable_unlock(); return rc; } sidetable_unlock(); //:如果没采用优化的isa指针,则直接返回sidetable中的值 return sidetable_retainCount(); }
从上面的代码我们可以得出:
retainCount = isa.extra_rc + sidetable_getExtraRC_nolock
,即引用计数=isa指针中存储的引用计数+sidetable中存储的引用计数那么
sidetable_getExtraRC_nolock
是如何从sideTable中获取retainCount
的呢?
下面我们来看下这个方法的实现。size_t objc_object::sidetable_getExtraRC_nolock() { // assert(isa.nonpointer); //key是 this,存储了每个对象的table SideTable& table = SideTables()[this]; //找到 it 否则返回0 RefcountMap::iterator it = table.refcnts.find(this); // 这里返回的it是RefcountMap类型 it == table.refcnts.end() // 表示在sidetable中没有找到this对应的引用计数则直接返回0 if (it == table.refcnts.end()) return 0; // RefcountMap 结构的second值为引用计数值 // DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap; else return it->second >> SIDE_TABLE_RC_SHIFT; }
了解了SideTable的RefcountMap,下面我们接着看另外一个属性weak_table
weak_table
我们都知道weak_table是对象弱引用map,它记录了所有弱引用对象的集合。
我们先来看下
weak_table_t
的定义:// 全局的弱引用表 struct weak_table_t { // hash数组,用来存储弱引用对象的相关信息weak_entry_t weak_entry_t *weak_entries; // hash数组中的元素个数 size_t num_entries; // hash数组长度-1,会参与hash计算。 //(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个) uintptr_t mask; // 最大哈希偏移值 uintptr_t max_hash_displacement; };
weak_entries
实质上是一个hash数组,数组中存储weak_entry_t
类型的元素。weak_entry_t的定义如下/** * The internal structure stored in the weak references table. * It maintains and stores * a hash set of weak references pointing to an object. * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set * is instead a small inline array. */ //inline_referrers数组中可以存放元素的最大个数 如果超过了这个个数就会使用referrers 存放 #define WEAK_INLINE_COUNT 4 // out_of_line_ness field overlaps with the low two bits of inline_referrers[1]. // inline_referrers[1] is a DisguisedPtr of a pointer-aligned address. // The low two bits of a pointer-aligned DisguisedPtr will always be 0b00 // (disguised nil or 0x80..00) or 0b11 (any other address). // Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state. // DisguisedPtr方法返回的hash值得最低2个字节应该是0b00或0b11,因此可以用out_of_line_ness // == 0b10来表明当前是否在使用数组或动态数组来保存引用该对象的列表。 #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { // 被弱引用的对象 DisguisedPtr<objc_object> referent; // 联合结构 两种结构共同占用一块内存空间 两种结构互斥 union { // 弱引用 被弱引用对象的列表 struct { // 弱引用该对象的对象列表的动态数组 weak_referrer_t *referrers; // 是否使用动态数组标记位 uintptr_t out_of_line_ness : 2; // 动态数组中元素的个数 uintptr_t num_refs : PTR_MINUS_2; // 用于hash确定动态数组index,值实际上是动态数组空间长度-1(它和num_refs不一样, // 这里是记录的是数组中位置的个数,而不是数组中实际存储的元素个数)。 uintptr_t mask; // 最大哈希偏移值 uintptr_t max_hash_displacement; }; struct { // inline_referrers 数组 当不使用动态数组时使用 最大个数为4 weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; }
从上面的介绍我们可以总结
SideTables
和SideTable
以及weak_table_t
在层级上的关系图如下:上图是从数据结构的角度来看弱引用的保存,下面我们来看下从垂直方向来看
从上面的总结中我们可以看到,弱引用的存储实际上一个三级的哈希表,通过一层层的索引找到或者存储对应的弱引用。那当向
weak_table_t
中插入或查找某个元素时是如何操作的呢?算法是什么样的呢?weak_entry_for_referent
/ 在weak_table中查找所有弱引用referent的对象 static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { assert(referent); //获取这个weak_table_t中所有的弱引用对象 weak_entry_t *weak_entries = weak_table->weak_entries; if (!weak_entries) return nil; //hash_pointer 哈希函数 传入的是 objc_object *key // weak_table->mask = weaktable的容量-1 size_t begin = hash_pointer(referent) & weak_table->mask; size_t index = begin; // 哈希冲突次数 size_t hash_displacement = 0; // 判断根据index获取到的弱引用对象数组中对应的weak_entry_t的弱引用对象是否为 // 外部传入的对象 while (weak_table->weak_entries[index].referent != referent) { // 开放地址法解决哈希冲突 // & weak_table->mask 是为了在下一个地址仍然没有找到外部传入对象时回到第一个对比的位置 index = (index+1) & weak_table->mask; if (index == begin) // 对比了所有数据 仍没有找到 直接报错 bad_weak_table(weak_table->weak_entries); // 哈希冲突次数++ hash_displacement++; // 最大哈希偏移值 表示已经遍历了数组中所有的元素 // 没有找到那么直接返回nil if (hash_displacement > weak_table->max_hash_displacement) { return nil; } } // 直接返回被弱引用的对象 return &weak_table->weak_entries[index]; }
上面就是根据对象地址获取所有弱引用该对象的的数组,基本逻辑都比较清晰,我们在遍历
weak_table->weak_entries
中的时候发现判断是否遍历完一遍的时候使用的方法index = (index+1) & weak_table->mask;
假设当前数组长度8,下标分别是0-7,上面
weak_table->mask
= 7 = 0111。下标 计算后结果 index = 0 index & mask = 0000 & 0111 = 0000 = 0 index = 1 index & mask = 0001 & 0111 = 0001 = 1 index = 2 index & mask = 0010 & 0111 = 0010 = 2 index = 3 index & mask = 0011 & 0111 = 0011 = 3 index = 4 index & mask = 0100 & 0111 = 0100 = 4 index = 5 index & mask = 0101 & 0111 = 0101 = 5 index = 6 index & mask = 0110 & 0111 = 0110 = 6 index = 7 index & mask = 0111 & 0111 = 0111 = 7 index = 8 index & mask = 1000 & 0111 = 0000 = 0 看完上面的计算相信大家都明白了这么做的真是意图了:
if (index == begin)
可以理解为:数组遍历完成,已经和数组中所有的元素做了对比。
随着某个对象被越来越多的对象弱引用,那么这个存放弱引用该对象的所有对象的数组也会越来越大。
hash表自动扩容
//weak_table_t扩容 // 参数 weak_table 要扩容的table new_size 目标大小 static void weak_resize(weak_table_t *weak_table, size_t new_size) { //weak_table的容量 size_t old_size = TABLE_SIZE(weak_table); // 取出weak_table中存放的所有实体 weak_entry_t *old_entries = weak_table->weak_entries; // 新创建一个weak_entry_t类型的数组 // 数组的大小是new_size * sizeof(weak_entry_t) weak_entry_t *new_entries = (weak_entry_t *) calloc(new_size, sizeof(weak_entry_t)); // 重置weak_table的mask的值 weak_table->mask = new_size - 1; // 将weak_table->weak_entries指向新创建的内存区域 注意 此时weak_table中没有任何数据 weak_table->weak_entries = new_entries; // 最大哈希偏移值重置为0 weak_table->max_hash_displacement = 0; //weak_table 中存储实体个数为0 weak_table->num_entries = 0; // restored by weak_entry_insert below // 旧数据的搬迁 if (old_entries) { weak_entry_t *entry; //old_entries看做数组中第一个元素的地址 由于数组是连续的存储空间 那么old_entries + old_size = 数组最后一个元素的地址 weak_entry_t *end = old_entries + old_size; // 遍历这些旧数据 for (entry = old_entries; entry < end; entry++) { //weak_entry_t的referent(referent是指被弱引用的对象) if (entry->referent) { // 将旧数据搬移到新的结构中 weak_entry_insert(weak_table, entry); } } // 释放所有的旧数据 free(old_entries); } }
从上面的代码中我们可以看到,哈希表的扩容主要分为下面几个步骤:
- 创建一个局部变量保存当前哈希表中保存的所有弱引用实体
- 新建一个容量是旧哈希表大小2倍的哈希表,同时重置
num_entries
、max_hash_displacement
、weak_entries
、mask
- 遍历之前保存的旧的数据 将数据按照顺序依次重新插入的新建的哈希表中
- 释放旧数据
我们看到将旧数据插入新数据的主要方法是
weak_entry_insert
,下面我们来仔细介绍下它:weak_entry_insert
// 向指定的weak_table_t中插入某个对象 // weak_table_t 目标 table // new_entry 被弱引用的对象 static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) { // 取出weak_table中所有弱引用的对象 weak_entry_t *weak_entries = weak_table->weak_entries; assert(weak_entries != nil); // 根据new_entry中被弱引用对象地址通过哈希算法 算出 弱引用new_entry->referent的对象存放的index size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); size_t index = begin; size_t hash_displacement = 0; // weak_entries[index].referent 如果不为空 表示已经有 while (weak_entries[index].referent != nil) { // 计算下一个要遍历的index index = (index+1) & weak_table->mask; // 遍历了所有元素发现weak_entries[index].referent 都不为nil if (index == begin) // 直接报错 bad_weak_table(weak_entries); // 哈希冲突次数++ hash_displacement++; } // 如果走到这里 表明index位置的元素referent=nil // 直接插入 weak_entries[index] = *new_entry; // 实体个数++ weak_table->num_entries++; // 最大哈希偏移值大于之前的记录 if (hash_displacement > weak_table->max_hash_displacement) { // 更新最大哈希偏移值 weak_table->max_hash_displacement = hash_displacement; } }
插入操作也很简单,主要分为下面几个步骤:
- 取出哈希表中所有弱引用对象的数据
- 遍历第一步取出的所有数据,找到第一个空位置
- 将要插入的实体插入到这个位置,同时更新当前
weak_table
中弱引用实体个数 - 重置
weak_table
中最大哈希冲突次数的值
插入的主要逻辑实际上并不复杂,但是我们发现最后一步
// 如果本次哈希偏移值大于之前记录的最大偏移值 则更新 if (hash_displacement > weak_table->max_hash_displacement) { // 修改最大哈希偏移值 weak_table->max_hash_displacement = hash_displacement; }
通过上面的代码我们发现,假设
weak_table
的weak_entries
最大容量为8,当前存放了3个被弱引用的对象且分别存放在下标为[0,1,2]中,同时要插入的对象new_entry
不再weak_entries
中,那么经过while循环,hash_displacement = 3
。实际上如果在没有哈希冲突的情况下我们通过hash_pointer
得到的index就应该是用来存放new_entry
的,但是因为存在哈希冲突,所以后移了3位后才找到合适的位置来存放new_entry
,因此hash_displacement
也被理解为,本应存放的位置距离实际存放位置的差值。综上,我们分析了哈希表中获取所有弱引用某个对象的对象数组,哈希表扩容方法,以及如何在哈希表中插入一个弱引用对象。
下面我们来看下新增和释放弱引用对象的方法
objc_initWeak
// 初始化一个weak 弱引用 // 参数location weak指针的地址 newObj weak指针指向的对象 id objc_initWeak(id *location, id newObj) { // 如果弱引用对象为空 if (!newObj) { *location = nil; return nil; } // 调用storeWeak return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }
-
id *location :__weak指针的地址,即weak指针取地址: &weakObj 。它是一个指针的地址。之所以要存储指针的地址,是因为最后我们要讲__weak指针指向的内容置为nil,如果仅存储指针的话,是不能够完成这个功能的。
-
id newObj :所引用的对象。即例子中的obj 。
从上面我们看出
objc_initWeak
实际上是调用了storeWeak
方法,且方法调用我们可以翻译为storeWeak<false, true, true> (location, (objc_object*)newObj)
storeWeak
enum CrashIfDeallocating { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true }; template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating> // HaveOld= true weak ptr之前是否已经指向了一个弱引用 // haveNew = true weak ptr是否需要指向一个新引用 // crashIfDeallocating = true 如果被弱引用的对象正在析构,此时再弱引用该对象,是否应该crash // crashIfDeallocating = false 将存储的数据置为nil // *location 代表weak 指针的地址 // newObj 被weak引用的对象。 static id storeWeak(id *location, objc_object *newObj) { assert(haveOld || haveNew); // 如果没有新值赋值 判断newObj 是否为空 否则断言 if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; retry: // 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable if (haveOld) { // 根据传入的地址获取到旧的值 oldObj = *location; // 根据旧值的地址获取到旧值所存在的SideTable 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; } // 加锁管理一对 side tables,防止多线程中竞争冲突 SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); // location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改 if (haveOld && *location != oldObj) { // 解锁后重试 SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // 保证弱引用对象的 isa 都被初始化,防止弱引用和 +initialize 之间发生死锁, // 也就是避免 +initialize 中调用了 storeWeak 方法,而在 storeWeak 方法中 weak_register_no_lock // 方法中用到对象的 isa 还没有初始化完成的情况 if (haveNew && newObj) { Class cls = newObj->getIsa(); // 如果cls还没有初始化,先初始化,再尝试设置weak if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 发送 +initialize 消息到未初始化的类 _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); // 如果该类还没有初始化完成,例如在 +initialize 中调用了 storeWeak 方法, // 也就是会进入这里面,进而设置 previouslyInitializedClass 以在重试时识别它 // 这里记录一下previouslyInitializedClass, 防止改if分支再次进入 previouslyInitializedClass = cls; // 重新获取一遍newObj,这时的newObj应该已经初始化过了 goto retry; } } // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址 if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // 如果weak_ptr需要弱引用新的对象newObj if (haveNew) { // (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); // (2) 更新newObj的isa的weakly_referenced bit标志位 if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1 // 将weak ptr指向object *location = (id)newObj; } else { // No new value. The storage is not changed. } // 解锁,其他线程可以访问oldTable, newTable了 SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1 return (id)newObj; }
storeWeak方法有点长,这也是weak引用的核心实现部分。其实核心也就实现了两个功能:
将weak指针的地址
location
存入到obj对应的weak_entry_t的数组(链表)中,用于在obj析构时,通过该数组(链表)找到所有其weak指针引用,并将指针指向的地址(location
)置为nil。如果启用了isa优化,则将obj的isa_t的
weakly_referenced
位置1。置位1的作用主要是为了标记obj被weak引用了,当dealloc时,runtime会根据weakly_referenced
标志位来判断是否需要查找obj对应的weak_entry_t,并将引用置为nil。上面的方法中,我们看到插入新值的方法为
weak_register_no_lock
,清除旧值的方法为weak_unregister_no_lock
,下面我们来看下这两个方法:weak_register_no_lock
/ 添加对某个对象的新的弱引用指针 // weak_table 目标被弱引用对象所存储的表 // referent_id 被所引用的对象 // referrer_id 要被添加的弱引用指针 // 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; // 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作 if (!referent || referent->isTaggedPointer()) return referent_id; // 确保被引用的对象可用(没有在析构,同时应该支持weak引用) bool deallocating; // referent 是否有自定义的释放方法 if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { // referent是否支持weak引用 BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); // 如果referent不能够被weak引用,则直接返回nil if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } // 调用referent的SEL_allowsWeakReference方法来判断是否正在被释放 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; } } // 对象没有被正在释放 weak_entry_t *entry; // 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中 // 如果能找到weak_entry,则讲referrer插入到weak_entry中 if ((entry = weak_entry_for_referent(weak_table, referent))) { // 将referrer插入到weak_entry_t的引用数组中 append_referrer(entry, referrer); } else { // 创建一个新的weak_entry_t ,并将referrer插入到weak_entry_t的引用数组中 weak_entry_t new_entry(referent, referrer); // weak_table的weak_entry_t 数组是否需要动态增长,若需要,则会扩容一倍 weak_grow_maybe(weak_table); // 将weak_entry_t插入到weak_table中 weak_entry_insert(weak_table, &new_entry); } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; }
上面方法主要功能是:添加对某个对象的新的弱引用指针
- 过滤掉
isTaggedPointer
和弱引用对象正在被释放这两种情况后(这里需要判断是否有自定义的释放方法),然后根据crashIfDeallocating
参数确定是崩溃还是返回nil - 如果对象没有正在被释放,那么从
weak_table
中取出指向referent
的弱引用指针实体,如果weak_table
中存在指向referent
的指针数组那么在这个数组中添加要新增的指针 - 如果
weak_table
没有找到指向referent
的弱指针数组,那么新建一个weak_entry_t
对象,将这个对象拆入到weak_table
中(需要判断weak_table是否需要扩容)
下面我们来看下具体的插入方法:
append_referrer追加
// 在entry对象的弱引用数组中追加一个新的弱引用指针new_referrer // entry 被弱引用的对象 // new_referrer 弱引用entry的指针 static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) { // 如果entry中弱引用指针没有超过了4个 表示弱引用指针存放在inline_referrers中 // weak_entry 尚未使用动态数组 if (! entry->out_of_line()) { // 遍历inline_referrers数组找到第一个为空的位置 将目标指针插入 尾部追加 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; } } // 如果entry中弱引用指针==4个 // 新创建一个weak_referrer_t数组 大小为4(WEAK_INLINE_COUNT) // 如果inline_referrers的位置已经存满了,则要转型为referrers,做动态数组。 weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); // 遍历inline_referrers 将数据放在新创建的临时数组中 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { new_referrers[i] = entry->inline_referrers[i]; } // 弱引用指针的存储改为存放到entry->referrers(entry->inline_referrers -> entry->referrers) entry->referrers = new_referrers; // 更新弱引用个数 entry->num_refs = WEAK_INLINE_COUNT; //更新是否使用动态数组标记位 entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; // 更新mask和最大哈希偏移值 entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } assert(entry->out_of_line()); // 如果只想entry的弱引用个数大于4 // 弱引用个数是否已超过数组容量的3/4 if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 如果已超过 那么先扩容在插入 return grow_refs_and_insert(entry, new_referrer); } // 如果不需要扩容,直接插入到weak_entry中 // 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; // 由低到高遍历entry->referrers 找到第一个空位置 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; } // 将new_referrer插入到数组的第index个位置 weak_referrer_t &ref = entry->referrers[index]; ref = new_referrer; // 弱引用计个数+1 entry->num_refs++; }
插入的过程主要分下面三种情况:
- 如果
inline_referrers
没有存储满,直接存储到inline_referrers
中 - 如果
inline_referrers
个数是4个了,在插入,就需要将inline_referrers
拷贝到referrers
,然后进入第三步。 - 如果
inline_referrers
存储满了,判断是否需要扩容,然后将数据存储到referrers
中。
下面我们来看下扩容的方法:
grow_refs_and_insert
// entry 中存放弱引用指针数组 扩容 // weak_entry_t 要扩容的对象 // new_referrer 要插入的指向entry->referent弱引用指针 __attribute__((noinline, used)) static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { assert(entry->out_of_line()); // 获取entry当前的大小 size_t old_size = TABLE_SIZE(entry); // 新的大小为旧的大小的2倍 size_t new_size = old_size ? old_size * 2 : 8; // 获取weak_entry_t中存储的弱引用指针个数 size_t num_refs = entry->num_refs; //获取entry中旧的引用数组 weak_referrer_t *old_refs = entry->referrers; // 更新entry->mask 这里是为了后续申请内存空间使用 entry->mask = new_size - 1; // 创建一个新的entry->referrers数组 // #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0) // TABLE_SIZE 获取的数组大小是 mask+1 = new_size entry->referrers = (weak_referrer_t *) calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t)); // 重置num_refs和max_hash_displacement entry->num_refs = 0; entry->max_hash_displacement = 0; // 将old_refs中的数据重新插入到新创建entry->referrers中 for (size_t i = 0; i < old_size && num_refs > 0; i++) { if (old_refs[i] != nil) { append_referrer(entry, old_refs[i]); num_refs--; } } // 将new_referrer插入到扩容后的entry中 append_referrer(entry, new_referrer); if (old_refs) free(old_refs); }
看完了新增弱引用指针的操作,接下来我们看下如何删除弱引用指针即
weak_unregister_no_lock
weak_unregister_no_lock
// 将 weak ptr地址 从obj的weak_entry_t中移除 // 参数weak_table 全局弱引用表 // referent_id 弱引用所指向的对象 // referrer_id 弱引用指针地址 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_table中指向被弱引用对象的所有指针 类型为 weak_entry_t if ((entry = weak_entry_for_referent(weak_table, referent))) { // 从数组中删除当前这个弱引用指针 remove_referrer(entry, referrer); bool empty = true; // 弱引用referent对象的弱引用指针是否为空 if (entry->out_of_line() && entry->num_refs != 0) { empty = false; } else { // 如果referrer数组中为空 那么判断inline_referrers中是否为空 如果为空empty=true for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i]) { empty = false; break; } } } // 如果为空 则证明没有其他指针指向这个被所引用的对象 if (empty) { // 将这个实体从weak_table中移除 weak_entry_remove(weak_table, entry); } } // Do not set *referrer = nil. objc_storeWeak() requires that the // value not change. }
weak_unregister_no_lock
的实现逻辑比较简单,其实主要的操作为:- 首先,它会在
weak_table
中找出referent
对应的weak_entry_t
- 在
weak_entry_t
中移除referrer
- 移除元素后,判断此时
weak_entry_t
中是否还有元素(empty==true?)
- 如果此时
weak_entry_t
已经没有元素了,则需要将weak_entry_t从weak_table
中移除
而对于
remove_referrer
方法,我们来简单的看下他的实现:remove_referrer
// 删除old_referrer集合中的referrers // 参数 entry 被弱引用对象 // 参数 old_referrer 要删除的弱引用指针 static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) { // 指向entry的弱引用指针不超过4个 if (! entry->out_of_line()) { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { // 遍历inline_referrers数组如果找到直接置空 if (entry->inline_referrers[i] == old_referrer) { entry->inline_referrers[i] = nil; return; } } // 如果没有找到 则报错 弱引用指针小于4个且在inline_referrers中没有找到 _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; } // 哈希函数 判断这个旧的弱引用指针存放的位置 size_t begin = w_hash_pointer(old_referrer) & (entry->mask); size_t index = begin; size_t hash_displacement = 0; // 遍历entry->referrers数组查找old_referrer while (entry->referrers[index] != old_referrer) { // 如果没有在指定index找到 那么取下一个位置的值比较 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; } } // 走到这一步说明在entry->referrers中的index位置找到了值为old_referrer的引用 // 将数组的这个位置置空 entry->referrers[index] = nil; // 弱引用个数-1 entry->num_refs--; }
上面的描述也很简单,大概的流程为:
- 在
entry->inline_referrers
中一次查找值为old_referrer
的指针 如果找到就清空如果没找到报错 - 在
entry->referrers
中查找值为old_referrer
的指针,如果找到则置空同时entry->num_refs
做-1操作(使用inline_referrers
存储时不会更新num_refs
值因此移除也不用-1)
我们在删除指向某个对象的某个弱引用指针之后,还会对存储指向该对象的弱引用指针数组做判空操作,如果发现数组为空,那表示目前没有弱引用指针指向这个对象,那我们需要将这个对象从
weak_table
中移除。下面我们来看下移除方法weak_entry_remove
。weak_entry_remove
//从weak_table中移除entry (指向entry的弱引用指针数为0) static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // 如果弱引用指针超过4个(弱引用指针存放在entry->referrers中) if (entry->out_of_line()) // 释放entry->referrers中所有数据 free(entry->referrers); bzero(entry, sizeof(*entry)); //num_entries-1 weak_table->num_entries--; //weak_table是否需要锁绒 weak_compact_maybe(weak_table); }
上面方法的主要操作为:
- 将没有弱引用的对象从全局的
weak_table
中移除 - 减少
weak_table
中存储的弱引用对象个数 - 判断
weak_table
是否需要缩小容量
上面的所有就是当我们将一个obj作weak引用时,所发生的事情。那么,当obj释放时,所有weak引用它的指针又是如何自动设置为nil的呢?接下来我们来看一下obj释放时,所发生的事情。
Dealloc
当对象引用计数为0时,runtime会调用_objc_rootDealloc方法来析构对象,实现如下:
- (void)dealloc { _objc_rootDealloc(self); } void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); }
_objc_rootDealloc
又会调用objc_object的rootDealloc
方法rootDealloc
inline void objc_object::rootDealloc() { // 判断object是否采用了Tagged Pointer计数,如果是,则不进行任何析构操作。 if (isTaggedPointer()) return; // fixme necessary? //接下来判断对象是否采用了优化的isa计数方式(isa.nonpointer) // 对象没有被weak引用!isa.weakly_referenced // 没有关联对象!isa.has_assoc // 没有自定义的C++析构方法!isa.has_cxx_dtor // 没有用到sideTable来做引用计数 !isa.has_sidetable_rc // 如果满足条件 则可以快速释放 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); } }
因此根据上面代码判断,如果obj被weak引用了,应该进入
object_dispose((id)this)
分支,下面我们来看下object_dispose
方法:object_dispose
id object_dispose(id obj) { if (!obj) return nil; // 析构obj objc_destructInstance(obj); // 释放内存 free(obj); return nil; }
析构obj主要是看
objc_destructInstance
方法,下面我们来看下这个方法的实现objc_destructInstance
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. //c++析构函数 bool cxx = obj->hasCxxDtor(); //关联函数 bool assoc = obj->hasAssociatedObjects(); // 如果有c++析构函数 则调用c++析构函数. if (cxx) object_cxxDestruct(obj); // 如果有关联对象则移除关联对象 if (assoc) _object_remove_assocations(obj); // 清理相关的引用 obj->clearDeallocating(); } return obj; }
清理相关引用方法主要是在
clearDeallocating
中实现的,下面我们再来看下这个方法:clearDeallocating
//正在清除side table 和weakly referenced inline void objc_object::clearDeallocating() { // obj是否采用了优化isa引用计数 if (slowpath(!isa.nonpointer)) { //没有采用优化isa引用计数 清理obj存储在sideTable中的引用计数等信息 sidetable_clearDeallocating(); } // 启用了isa优化,则判断是否使用了sideTable // 使用的原因是因为做了weak引用(isa.weakly_referenced ) 或 使用了sideTable的辅助引用计数(isa.has_sidetable_rc) else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs and/or side table data. //释放weak 和引用计数 clearDeallocating_slow(); } assert(!sidetable_present()); }
这里的清理方法有两个分别为
sidetable_clearDeallocating
和clearDeallocating_slow
,我们先来看下
clearDeallocating_slow
:clearDeallocating_slow
NEVER_INLINE void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); // 在全局的SideTables中,以this指针为key,找到对应的SideTable SideTable& table = SideTables()[this]; table.lock(); 如果obj被弱引用 if (isa.weakly_referenced) { 在SideTable的weak_table中对this进行清理工作 weak_clear_no_lock(&table.weak_table, (id)this); } // 如果采用了SideTable做引用计数 if (isa.has_sidetable_rc) { //在SideTable的引用计数中移除this table.refcnts.erase(this); } table.unlock(); }
这里调用了
weak_clear_no_lock
来做weak_table
的清理工作,同时将所有weak引用该对象的ptr置为nil。weak_clear_no_lock
//清理weak_table,同时将所有weak引用该对象的ptr置为nil void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; // 找到referent在weak_table中对应的weak_entry_t 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; } // 找出weak引用referent的weak 指针地址数组以及数组长度 weak_referrer_t *referrers; size_t count; // 是否使用动态数组 if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } // 遍历所有的所引用weak指针 for (size_t i = 0; i < count; ++i) { // 取出每个weak ptr的地址 objc_object **referrer = referrers[i]; if (referrer) { // 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因 if (*referrer == referent) { *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(); } } } // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table weak_entry_remove(weak_table, entry); }
上面就是为什么当对象析构时,所有弱引用该对象的指针都会被设置为nil的原因。
总结
综上我们讲述了SideTable的结构,以及如何使用SideTable存储和清除对象和指向这些对象的指针地址。从而在侧面验证了弱引用的存储方式以及在对象释放时如何将弱引用的指针置空。读完这篇文章相信你对于SideTable结构和弱引用已经有了一个比较全面的认识。
- 保证原子属性的自旋锁
-
iOS开发-weak引用以及sidetable表
2020-04-28 23:28:18文章目录 __weak sidetable 对象的释放 __weak id obj = [[NSObject alloc] init]; @autoreleasepool { id __weak obj1 = obj; NSLog(@"%@",obj1); } 括号内的代码编译器会转化为: id obj1; objc_initWeak(&obj1,...__weak
id obj = [[NSObject alloc] init]; @autoreleasepool { id __weak obj1 = obj; NSLog(@"%@",obj1); }
括号内的代码编译器会转化为:
id obj1; objc_initWeak(&obj1,obj); id tmp = objc_loadWeakRetained(&obj1); objc_autorelease(tmp); NSLog(@"%@",tmp); objc_destroyWeak(&obj1);
- 调用
objc_initWeak
存入sidetable
表 objc_loadWeakRetain
返回自身,并引用计数+1(refcnts
的value
+固定增量值
)objc_autorelease
加入自动释放池objc_destroyWeak
将weak
指针从weak_table_t
表中移除
那么
sidetable
这个表是什么?sidetable
并不是只有弱引用对象才有这个
sidetable
,objc_object
对象拥有isa
指针,这个指针中存储了许多信息,其中几位就存储了引用计数,但是当引用计数大于无法使用位存储时,也会创建sidetable
,并使用sidetable
进行引用计数。同时objc_initWeak
也是创建sidetable
的。这里贴出了
isa
源码中arm64
位架构# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; //有关联的对象 uintptr_t has_cxx_dtor : 1; //c++ 析构 uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 uintptr_t magic : 6; //判断当前对象是真的对象还是没有初始化的空间 uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; //是否有sidetable引用计数 uintptr_t extra_rc : 19; //引用计数 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) };
has_sidetable_rc
即表示是否有sidetable
进行引用计数。值得注意的是isa_t是isa优化后的结构,最开始的isa是直接是一个
objc_class
类型的,苹果后续对其进行了优化,能存储更多的信息。
值得注意的tagged pointer并没有这个结构,它就是一个16字节地址(64位系统上),isa相关sidetable
算一个链表结构,sidetables
才算一个hash表,利用数组方便查找
和链表的方便插入删除
特性构成,所以sidetables
相当于一个数组,里面存储sidetable
,sidetable
中的存储结构就是weak_table_t
,weak_table_t
中存储着不同的对象weak_entry_t
,weak_entry_t
中存储这个对象的所有的弱引用指针。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); }; struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; }; struct weak_entry_t { DisguisedPtr<objc_object> referent; union { 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]; }; }; ... };
hash表无论是在iOS还是java开发中都常常遇到,以空间换时间的方式意味着数组并不能无限大,所以一般都是64个,然后数组中存储链表,可以看看Notification中的这个hash表 见博文
图来自 https://blog.csdn.net/u013378438/article/details/82790332
对象的释放
dealloc
方法最终会走向rootDealloc
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && //为1表示优化后的isa即isa_t !isa.weakly_referenced && //无弱引用,不需要释放sidetable !isa.has_assoc && //无关联属性,例如分类中添加的 !isa.has_cxx_dtor && //不需要调用c++析构方法 !isa.has_sidetable_rc)) //无sidetable引用计数 { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); //正常释放 } }
object_dispose
最终会跳转到/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is 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); //c++析构函数调用 if (assoc) _object_remove_assocations(obj); //关联对象移除 是一个hash表 obj->clearDeallocating(); //这里进行弱引用表sidetable的相关释放操作,包括表的释放以及引用计数 } return obj; } | | V 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()); } | | V // 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]; 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(); } #endif
所以
dealloc
包括以下几个步骤c++
析构函数调用关联对象(例如使用runtime在分类中关联变量)
移除,是一个hash表
来存储- 这里进行
弱引用表sidetable
的相关释放操作,包括表的释放
以及引用计数
,即weak
指针置为nil
的操作就在这里
值得注意的是
dealloc
方法是对象引用计数
在哪个线程为0
,则在哪个线程调用dealloc
方法,所以不一定在主线程执行 - 调用
-
iOS弱引用表 SideTable weak_table_t weak_entry_t
2022-01-07 14:32:58} //获取StripedMap对象,一般都使用这个函数获取StripedMap对象, //从而操作引用计数和所引用计数 static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf)...对iOS的weak弱引用的底层方法都加了注释
尤其是objc-weak.mm文件中的方法做了全面的注释一、DisguisedPtr伪装指针类介绍
主要是用来把对象的指针映射到long类型的数值,来保存对象的指针,至于为什么不直接保存指针,估计是处于安全考虑,
防止空指针造成的坏的影响;地层大量使用了DisguisedPtr,DisguisedPtr也不是很复杂;
可以看出DisguisedPtr是个模版类,可以看作是iOS中的范型,
里面定义了一个属性value,用来保存处理后的对象指针;是个unsigned long类型,
和其他几个函数;template <typename T> class DisguisedPtr { uintptr_t value;//用来保存处理后的对象指针;是个unsigned long类型, static uintptr_t disguise(T* ptr) { //把对象的指针转成unsigned long类型 return -(uintptr_t)ptr; } static T* undisguise(uintptr_t val) { //还原对象指针 return (T*)-val; } public: DisguisedPtr() { } //构造函数,通过disguise函数转换之后,把对象指针保存在value中 DisguisedPtr(T* ptr) : value(disguise(ptr)) { } DisguisedPtr(const DisguisedPtr<T>& ptr) : value(ptr.value) { } DisguisedPtr<T>& operator = (T* rhs) { value = disguise(rhs); return *this; } DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) { value = rhs.value; return *this; } //重载了几个运算符,使用undisguise函数转换后,用来获取保存的对象指针, operator T* () const { return undisguise(value); } T* operator -> () const { return undisguise(value); } T& operator * () const { return *undisguise(value); } T& operator [] (size_t i) const { return undisguise(value)[i]; } // pointer arithmetic operators omitted // because we don't currently use them anywhere };
二、StripedMap介绍
StripedMap中默认保存8张表,使用哈希计数对象对应的表enum { CacheLineSize = 64 }; // StripedMap<T> is a map of void* -> T, sized appropriately // for cache-friendly lock striping. // For example, this may be used as StripedMap<spinlock_t> // or as StripedMap<SomeStruct> where SomeStruct stores a spin lock. //保存8张SideTable表,通过哈希对象的指针,确定保存的表的位置, template<typename T> class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 };//苹果手机iOS系统默认8张 #else enum { StripeCount = 64 }; #endif //里面又定义一个结构体,value属性就是SideTable,使用64字节对齐 struct PaddedT { T value alignas(CacheLineSize); }; //保存8张表的数组 PaddedT array[StripeCount]; //通过哈希对象的指针计算出array的索引位置 static unsigned int indexForPointer(const void *p) { uintptr_t addr = reinterpret_cast<uintptr_t>(p); return ((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: //重载运算符,用来获取对应的表 T& operator[] (const void *p) { return array[indexForPointer(p)].value; } const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; } // Shortcuts for StripedMaps of locks. void lockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.lock(); } } void unlockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.unlock(); } } void forceResetAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.forceReset(); } } void defineLockOrder() { for (unsigned int i = 1; i < StripeCount; i++) { lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value); } } void precedeLock(const void *newlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock); } void succeedLock(const void *oldlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(oldlock, &array[0].value); } const void *getLock(int i) { if (i < StripeCount) return &array[i].value; else return nil; } #if DEBUG StripedMap() { // Verify alignment expectations. uintptr_t base = (uintptr_t)&array[0].value; uintptr_t delta = (uintptr_t)&array[1].value - base; assert(delta % CacheLineSize == 0); assert(base % CacheLineSize == 0); } #else constexpr StripedMap() {} #endif };
//初始化一个全局对象数组,用来保存引用计数和弱引用计数 //可以看出这个数组的内存长度等于StripedMap对象所占的内存长度 //所以这个数据也可以看作是StripedMap对象 alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)]; //初始化函数,StripedMap保存了8张SideTable表 static void SideTableInit() { new (SideTableBuf) StripedMap<SideTable>(); } //获取StripedMap对象,一般都使用这个函数获取StripedMap对象, //从而操作引用计数和所引用计数 static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); }
三、weak_table_t
用来保存弱引用,是个结构体,里面有4个属性,struct weak_table_t { weak_entry_t *weak_entries;//保存每个对象的弱引用的数组, size_t num_entries;//数组的元素个数 uintptr_t mask;//weak_entries数组的长度-1,不是数组元素个数-1;因为数组的长度可能是100,但是元素个数可能是9个; 这个属性另一个作用是参与到哈希运算,得到对象的对应的数组索引index; uintptr_t max_hash_displacement;最大冲突或者异常次数,如果运算过程中冲突或者异常次数超过这个值max_hash_displacement则有问题 };
四、weak_entry_t
弱引用实体,一个对象对应一个weak_entry_t,保存对象的弱引用;
保存弱应用的是个联合体,当弱引用小于等于4个的时候,直接用inline_referrers数组保存
大于四个时用referrers动态数组typedef DisguisedPtr<objc_object *> weak_referrer_t; #if __LP64__ #define PTR_MINUS_2 62 #else #define PTR_MINUS_2 30 #endif #define WEAK_INLINE_COUNT 4 #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { DisguisedPtr<objc_object> referent;//被弱引用的对象 // union { struct { weak_referrer_t *referrers;//动态数组保存弱引用的指针 uintptr_t out_of_line_ness : 2;//记录是否需要使用动态数组 uintptr_t num_refs : PTR_MINUS_2;//动态数组中的元素个数 uintptr_t mask;//动态数组的长度-1,作用同weak_table_t的mask uintptr_t max_hash_displacement;//最大冲突或者异常次数,如果运算过程中冲突或者异常次数超过这个值max_hash_displacement则有问题 }; struct { //初始化时默认使用的数组 weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; //判断是否大于4个,师傅需要动态数组 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; } //构造函数,里面默认使用inline_referrers数组,当数据大于4个时,会改成动态数组referrers 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; } } };
五、下面介绍的是对弱引用的操作函数,包括添加,删除弱引用
下面是objc-weak.mm文件全部内容/* * Copyright (c) 2010-2011 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "objc-private.h" #include "objc-weak.h" #include <stdint.h> #include <stdbool.h> #include <sys/types.h> #include <libkern/OSAtomic.h> //获取weak_entry_t保存的弱引用个数,保存在mask中的时候会-1,现在取出来的时候再+1,就是弱引用数组的长度了 #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0) //提前声明函数,c语法 static void append_referrer(weak_entry_t *entry, objc_object **new_referrer); //错误处理函数 BREAKPOINT_FUNCTION( void objc_weak_error(void) ); //错误处理函数 static void bad_weak_table(weak_entry_t *entries) { _objc_fatal("bad weak table at %p. This may be a runtime bug or a " "memory error somewhere else.", entries); } /** * Unique hash function for object pointers only. * * @param key The object pointer * * @return Size unrestricted hash of pointer. */ //哈希运算,把对象的指针进行哈希运算,用来计算哈希表的key static inline uintptr_t hash_pointer(objc_object *key) { return ptr_hash((uintptr_t)key); } /** * Unique hash function for weak object pointers only. * * @param key The weak object pointer. * * @return Size unrestricted hash of pointer. */ //哈希运算,把对象的指针进行哈希运算,用来计算哈希表的key static inline uintptr_t w_hash_pointer(objc_object **key) { return ptr_hash((uintptr_t)key); } /** * Grow the entry's hash table of referrers. Rehashes each * of the referrers. * * @param entry Weak pointer hash set for a particular object. */ //用来对weak_entry_t中的数组进行扩容, //如果数组元素大于4个,或者大于数组长度的3/4时,调用此函数进行扩容; //数组扩容后的长度是原来长度的1倍 __attribute__((noinline, used)) static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { assert(entry->out_of_line()); size_t old_size = TABLE_SIZE(entry);//获取原来数组的长度 size_t new_size = old_size ? old_size * 2 : 8;//进行扩容,长度变成原来的2倍 size_t num_refs = entry->num_refs;//原来的弱引用个数 weak_referrer_t *old_refs = entry->referrers;//获取原来的数组指针,暂时保存在old_refs中 entry->mask = new_size - 1;//把新的长度-1保存在mask,; entry->referrers = (weak_referrer_t *) calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));//重新创建一块内存,用来存储弱引用指针,并设置给referrers属性 entry->num_refs = 0;//元素个数设置0 entry->max_hash_displacement = 0;//最大冲突数设置0 for (size_t i = 0; i < old_size && num_refs > 0; i++) { if (old_refs[i] != nil) { //这里是把原来的弱引用按顺序保存到新创建的数组中 append_referrer(entry, old_refs[i]); num_refs--; } } // Insert append_referrer(entry, new_referrer);把新的弱引用保存新的数组中 if (old_refs) free(old_refs);//最后释放旧的弱引用内存 } /** * 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) { //先判断弱引用数组是否大于大于4个,如果不大于4,就走这里 if (! entry->out_of_line()) { // Try to insert inline. for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { //先判断数组中对应的元素是否为nil,如果是nil,说明弱引用的指针还不到4个,就直接保存,然后return; //如果都不是nil,说明默认的数组已经存了4个元素了,就进行下一步; if (entry->inline_referrers[i] == nil) { //直接保存到默认的固定长度为4的数组中 entry->inline_referrers[i] = new_referrer; //返回 return; } } //到这里,说明弱引用的数组超过4个了, //从新申请一块内存,用来保存弱引用指针; 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;//最大异常次数0 } assert(entry->out_of_line()); //在这里判断是否需要扩容 //如果元素个数大于数组长度的3/4时,就需要扩容 if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { //调这个方法去扩容 return grow_refs_and_insert(entry, new_referrer); } //通过哈希计算对象的指针对应的key,& (entry->mask)这个运算是为了防止计算的key超过数组的长度 size_t begin = w_hash_pointer(new_referrer) & (entry->mask); size_t index = begin;//保存哈希后的key size_t hash_displacement = 0;//保存最大异常次数 //这一步是计算,找到一个索引为index的位置上元素为nil的空位置,用来保存这个对象的弱引用指针 while (entry->referrers[index] != nil) { //如果不等nil,说明有这个key,然后就hash_displacement+1, hash_displacement++; //然后在把key+1计算新的key,直到找到空的位置,否则下面抱错 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++; } /** * Remove old_referrer from set of referrers, if it's present. * Does not remove duplicates, because duplicates should not exist. * * @todo this is slow if old_referrer is not present. Is this ever the case? * * @param entry The entry holding the referrers. * @param old_referrer The referrer to remove. */ //删除弱引用指针,分为从固定数组删除还是从动态数组删除 static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) { //如果弱引用指针的个数小于等于4个,直接从固定数组中删除 if (! entry->out_of_line()) { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == old_referrer) { //在这里找到指针,直接设置nil 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; } //如果大于4,就需要从动态数组删除 //哈希计算出对象的key 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,并把num_refs减1,说明元素个数少一个 entry->referrers[index] = nil; entry->num_refs--; } /** * Add new_entry to the object's table of weak references. * Does not check whether the referent is already in the table. */ //当一个对象第一次被弱引用时,需要把把生成的weak_entry_t保存在weak_table_t表中 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); //哈希referent计算出对象的key,& (weak_table->mask)是为了防止key超出弱引用数组的长度 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; } } //修改弱引用表的大小,因为多出来的用不到表,太占内存,可以清除掉不用的表 static void weak_resize(weak_table_t *weak_table, size_t new_size) { //获取旧的数组的长度 size_t old_size = TABLE_SIZE(weak_table); weak_entry_t *old_entries = weak_table->weak_entries;//暂时保存旧的数组 weak_entry_t *new_entries = (weak_entry_t *) calloc(new_size, sizeof(weak_entry_t));//根据新的长度,创建新的数组 weak_table->mask = new_size - 1;//保存数组长度,保存时进行了-1,获取时需要+1; weak_table->weak_entries = new_entries;//保存新的数组 weak_table->max_hash_displacement = 0; weak_table->num_entries = 0; // restored by weak_entry_insert below if (old_entries) { weak_entry_t *entry; weak_entry_t *end = old_entries + old_size; //通过循环,把旧的数组中的元素保存到新数组中 for (entry = old_entries; entry < end; entry++) { if (entry->referent) { weak_entry_insert(weak_table, entry); } } //最后释放旧数组 free(old_entries); } } // Grow the given zone's table of weak references if it is full. //对弱引用表进行扩容 static void weak_grow_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table); // Grow if at least 3/4 full. //如果弱引用表中的元素个数大于3/4时,就扩容一倍, if (weak_table->num_entries >= old_size * 3 / 4) { //调这个方法扩容 weak_resize(weak_table, old_size ? old_size*2 : 64); } } // Shrink the table if it is mostly empty. //对弱引用表进行缩减,删除空的表 static void weak_compact_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table); // Shrink if larger than 1024 buckets and at most 1/16 full. //如果弱引用表的长度大于1024,并且元素只有不到1/16,就进行缩减,把空表清除 if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) { weak_resize(weak_table, old_size / 8); // leaves new table no more than 1/2 full } } /** * Remove entry from the zone's table of weak references. */ //删除表,当一个对象释放时,没有弱引用了,就把这个表删掉 static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // remove entry //如果弱引用小于4,直接释放 if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof(*entry));//清空数据 weak_table->num_entries--;//减1 //判断是否需要缩减表 weak_compact_maybe(weak_table); } /** * 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; size_t index = begin; size_t hash_displacement = 0; //通过循环找到key 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]; } /** * 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))) { //删掉这个弱引用指针 remove_referrer(entry, referrer); bool 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); } } // Do not set *referrer = nil. objc_storeWeak() requires that the // value not change. } /** * 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, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; //前面这些判断是不是一个OC对象 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); } 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; //判断这个对象先前有没用弱引用表,如果有直接使用 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; } #if DEBUG bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id) { return weak_entry_for_referent(weak_table, (objc_object *)referent_id); } #endif /** * 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; 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]; if (referrer) { //通过循环,把弱引用指针设置为nil if (*referrer == referent) { *referrer = nil; } else if (*referrer) { _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); }
-
sidetable:边桌可为您的数据建立简单但有用的汇总表
2021-03-20 01:29:54安装和import sidetable 。然后通过您的DataFrame上的新.stb访问器对其进行访问。对于泰坦尼克号数据: df.stb.freq(['class'])将建立一个频率表,如下所示:班级数数百分累积计数累积百分比0第三491 55.1066 491 55... -
Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t
2018-10-11 18:04:40在runtime中,有四个数据结构非常重要,分别是SideTables,SideTable,weak_table_t和weak_entry_t。它们和对象的引用计数,以及weak引用相关。 关系 先说一下这四个数据结构的关系。 在runtime内存空间中,... -
Sidetable:一种高效的 Python 数据框处理工具!
2021-08-10 22:31:13Sidetable 就是一个开源 Python 库,它是一种可用于数据分析和探索的工具,作为 value_counts 和 crosstab 的功能组合使用的。在本文中,我们将更多地讨论和探索其功能。欢迎收藏学习、点赞支持。 安装 可以使用从 ... -
Python 使用Sidetable探索数据集
2022-01-18 10:20:04在对数据集进行探索时,我经常使用的语句包括 df.shape df.head()... 最近呢,又学习了一个新的数据探索包——Sidetable,懒得介绍了,百度有很多,其实看一下代码+结果,就知道咋用了,非常舒适~ 安装 pip install -i ... -
iOS Runtime面试题(runtime如何实现weak变量的自动置nil?知道SideTable吗?)
2019-11-07 09:45:42知道SideTable吗? runtime 对注册的类会进行布局,对于 weak 修饰的对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,... -
[iOS]-weak底层原理(sidetable相关,附带引用计数原理)
2022-07-23 15:37:58[iOS]-weak底层原理(sidetable相关,附带引用计数原理)