iOS开发者都知道,当一个对象被释放时,所有对这个对象弱引用的指针都会释放并置为nil,那么系统是如何存储这些弱引用对象的呢?又是如何在一个对象释放时,将这些指向即将释放对象的弱引用的指针置为nil的呢?下面我们通过分析SideTable的结构来进一步了解内存管理的弱引用存储细节。

结构

在runtime中,有四个数据结构非常重要,分别是SideTablesSideTableweak_table_tweak_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_SIMULATORenum { StripeCount = 8 };
#elseenum { StripeCount = 64 };
#endif// PaddedT 为一个结构体struct PaddedT {T value alignas(CacheLineSize);};// array 中存放着8个sidetablePaddedT 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)].valueT& 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,存储了每个对象的tableSideTable& table = SideTables()[this];//找到 it 否则返回0RefcountMap::iterator it = table.refcnts.find(this);// 这里返回的it是RefcountMap类型 it == table.refcnts.end() // 表示在sidetable中没有找到this对应的引用计数则直接返回0if (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_tweak_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 数组 当不使用动态数组时使用 最大个数为4weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];};};
}

从上面的介绍我们可以总结SideTablesSideTable以及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的容量-1size_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++;// 最大哈希偏移值 表示已经遍历了数组中所有的元素// 没有找到那么直接返回nilif (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;// 最大哈希偏移值重置为0weak_table->max_hash_displacement = 0;//weak_table 中存储实体个数为0weak_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_entriesmax_hash_displacementweak_entriesmask
  • 遍历之前保存的旧的数据 将数据按照顺序依次重新插入的新建的哈希表中
  • 释放旧数据

我们看到将旧数据插入新数据的主要方法是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的对象存放的indexsize_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) {// 计算下一个要遍历的indexindex = (index+1) & weak_table->mask;// 遍历了所有元素发现weak_entries[index].referent 都不为nilif (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_tableweak_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;}// 调用storeWeakreturn 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取出,赋值给oldTableif (haveOld) {// 根据传入的地址获取到旧的值oldObj = *location;// 根据旧值的地址获取到旧值所存在的SideTableoldTable = &SideTables()[oldObj];} else {// 如果weak ptr之前没有弱引用过一个obj,则oldTable = niloldTable = nil;}// 是否有新值 如果有if (haveNew) {// 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTablenewTable = &SideTables()[newObj];} else {// 如果weak ptr不需要引用一个新obj,则newTable = nilnewTable = 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还没有初始化,先初始化,再尝试设置weakif (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需要弱引用新的对象newObjif (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位置1return (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引用,则直接返回nilif ((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/4if (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_referrersize_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;// 弱引用计个数+1entry->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_sizeentry->referrers = (weak_referrer_t *)calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));// 重置num_refs和max_hash_displacemententry->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_tif ((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=truefor (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_referrerwhile (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;// 弱引用个数-1entry->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-1weak_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;// 析构objobjc_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_clearDeallocatingclearDeallocating_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,找到对应的SideTableSideTable& 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的引用计数中移除thistable.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_tweak_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 %pn", 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_tableweak_entry_remove(weak_table, entry);
}

上面就是为什么当对象析构时,所有弱引用该对象的指针都会被设置为nil的原因。

总结

综上我们讲述了SideTable的结构,以及如何使用SideTable存储和清除对象和指向这些对象的指针地址。从而在侧面验证了弱引用的存储方式以及在对象释放时如何将弱引用的指针置空。读完这篇文章相信你对于SideTable结构和弱引用已经有了一个比较全面的认识。

memset 结构体内指针_SideTable结构相关推荐

  1. 用结构体指针访问结构体中的结构体指针(结构体指针的嵌套)

    结构体中的结构体指针是使用 一.问题背景 二.代码 三.说明 一.问题背景   日常工作中没有编程的机会,所以只看得懂代码,现在需要重新写一段代码,实现固定格式存储数据,需要使用到结构体和结构体指针. ...

  2. C++结构体(结构体创建,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,const变量使用)

    C++结构体(结构体创建,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,const变量使用) 目录 C++结构体(结构体创建,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数, ...

  3. 【C++】结构体 - 定义和使用,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,结构体 const

    文章目录 1. 定义和使用 2. 结构体数组 3. 结构体指针 4. 结构体嵌套结构体 5. 结构体做函数参数 6. 结构体 const 1. 定义和使用 结构体属于用户自定义的数据类型,允许用户存储 ...

  4. C++结构体 结构体定义和使用、结构体数组、结构体指针、结构体嵌套结构体、结构体做函数参数

    C++结构体 第二章 C++结构体 1.结构体定义和使用 语法:struct 结构体名 { 结构体成员列表 }: 通过结构体创建变量的方式有三种: struct 结构体名 变量名 struct 结构体 ...

  5. 结构体指针和结构体变量

    文章目录 1.前言 2.内存空间上面的差别 3.访问上的差别 1.前言 今天在写题目的时候出现了一处小错误,就是由于结构体指针和结构体变量没有区分清楚,接下来谈一谈这两者的区别: 2.内存空间上面的差 ...

  6. 结构体内指针数组调用_指针的这些技巧你都掌握了吗

    点击上方蓝字"杜明c"一起玩耍 摘要 为什么需要用指针? 一些概念 数组指针 指针数组 指针数组和数组指针在内存中的关系 函数指针 函数指针例子 指针作为参数的传递 通过函数修改指 ...

  7. C语言结构体指针与结构体变量作形参的区别

    区别 结构体变量 结构体变量作为函数参数,传递的是结构体变量本身,是一种值传递 形参结构体变量成员值的改变不影响对应的实参构体变量成员值的改变 结构体指针 结构体指针作为函数参数,传递的是指向结构体变 ...

  8. c语言嵌套结构体数组,第22节 C语言结构体之结构体嵌套、结构体指针与结构体数组的代码实现...

    结构体 #include //第一步 struct Student { //学号 int no; //姓名 char name[20]; //性别 char sex[10]; //成绩 double ...

  9. 【C语言】结构体指针与结构体数组

    目录 一.结构体指针 二.结构体数组 1.结构体数组的定义 2.结构体数组的初始化 3.结构体数组的引用 4.结构体数组指针 一.结构体指针 与一般指针类似结构体也可以使用结构体指针进行引用使用.结构 ...

最新文章

  1. FFmpeg通过摄像头实现对视频流进行解码并显示测试代码(新接口)
  2. SAP RETAIL 分配规则里的哪些数据不会被带入分配表?
  3. 网络爬虫--SAX处理xml
  4. python和c学习-python与c++交互学习入门之5
  5. [Wannafly挑战赛2D-Delete]最短路
  6. 关于flex,好像有12个属性非常重要
  7. Python应用03 使用PyQT制作视频播放器
  8. 文本分类模型_文本分类中的经典深度学习模型
  9. 已收藏!java自学网址
  10. ORA-12737: Instant Client Light: unsupported server character set CHS16GBK/ZHS16GBK解决方案
  11. sublime_text_2 注册
  12. Idle进程的切换过程
  13. canvas动态风车
  14. Seq2Seq Attention模型
  15. Android8.1 audio之compressed offload流程(四十一)
  16. icmp回复报文_如果目标主机阻塞了,ICMP回显请求报文,我们可以
  17. 初试 Kubernetes 集群使用 CephFS 文件存储
  18. 拯救消防员!AI提前30秒预测火灾闪燃;12个Python项目的全流程:从构思、执行到部署;『系统设计』面试资料大全;高性能图数据处理和嵌入的Rust/Python库 | ShowMeAI资讯日报
  19. String类的Intern()方法
  20. 芯片行业是不是过热了

热门文章

  1. python中for循环和while循环的区别_Python中for循环和while循环有什么区别?
  2. 7个IntelliJ IDEA必备插件,提高编码效率
  3. 蓝桥杯练习系统习题-算法训练3
  4. springmvc教程--RESTful支持详解
  5. Android基于回调的事件处理
  6. Android的圆角按钮和按钮颜色
  7. (Java常用类)Object类
  8. pdf更新啦,快来瞧瞧!
  9. 经典笔试题: 二叉树中和为某一值的路径(路径总和)
  10. hdu1181变形课dfs/bfs/并查集三种解法(java)