【iOS开发】——weak底层原理

  • 复习retain
  • 复习release
  • weak
    • SideTable
      • weak表
    • weak的简述
    • weak的实现步骤
    • weak几个重要的实现函数
      • objc_initWeak 函数
      • objc_storeWeak()
      • weak_register_no_lock方法添加弱引用
        • weak_entry_for_referent取元素
        • append_referrer添加元素
      • weak_unregister_no_lock移除引用
    • weak在释放时底层都做了哪些事情
      • dealloc方法
      • 当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?
  • 总结

复习retain

先看一下retain源码的流程图

我们根据图来看一下retain的步骤:

  1. 第1步:若对象为TaggedPointer小对象,无需进行内存管理,直接返回;
  2. 第2步:若对象的isa没有经过优化,即!newisa.nonpointer成立,由于tryRetain=false,直接进入sidetable_retain方法,此方法本质是直接操作散列表,最后让目标对象的引用计数+1;
  3. 第3步:判断对象是否正在释放,若正在正在释放,则执行dealloc流程,释放弱引用表和引用计数表;
  4. 第4步:若对象的isa经过了优化,则执行newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry),即isa的位域extra_rc+1,且通过变量carry来判断位域extra_rc是否已满,如果位域extra_rc已满则执行newisa.extra_rc = RC_HALF,即将extra_rc满状态的一半拿出来存到extra_rc位域中,然后将另一半存储到散列表中,执行sidetable_addExtraRC_nolock(RC_HALF)函数;

复习release

同样我们先看一下release的流程图

  1. 第1步:若对象为TaggedPointer小对象,不需要做内存管理操作,直接返回;
  2. 第2步:若对象的isa没有经过优化,即!newisa.nonpointer成立,直接进入sidetable_release方法,此方法本质是直接操作散列表,最后让目标对象的引用计数-1;
  3. 第3步:判断是引用计数是否为0,如果是0则执行dealloc流程
  4. 第4步:若对象的isa经过优化,则执行newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry),即对象的isa位域extra_rc-1;且通过变量carry标识对象的isa的extra_rc是否为0, 如果对象的isa的extra_rc=0,则去访问散列表,判断对象在散列表中是否存在引用计数;
  5. 第五步:如果sidetable的引用计数为0,对象进行dealloc流程

weak

SideTable

我们先来学习一下SideTable因为后面会遇到:

SideTable 这个结构体,前辈给它总结了一个很形象的名字叫引用计数和弱引用依赖表,因为它主要用于管理对象的引用计数和 weak 表。在 NSObject.mm 中声明其数据结构:

struct SideTable {// 保证原子操作的自旋锁spinlock_t slock;// 引用计数的 hash 表RefcountMap refcnts;// weak 引用全局 hash 表weak_table_t weak_table;
}
  • slock是为了防止竞争选择的自旋锁
  • refcnts 是协助对象的 isa 指针的 extra_rc 共同引用计数的变量(对于对象结果,在后文提到)

至于最后一个weak_table_t我们来着重看一下:

weak表

weak表是一个弱引用表,实现为一个weak_table_t结构体,存储了某个对象相关的的所有的弱引用信息。其定义如下(具体定义在objc-weak.h中), 注意看系统的注释:

/**全局的弱引用表, 保存object作为key, weak_entry_t作为value* The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/
struct weak_table_t {// 保存了所有指向特地对象的 weak指针集合weak_entry_t *weak_entries;// weak_table_t中有多少个weak_entry_tsize_t    num_entries;// weak_entry_t数组的countuintptr_t mask;// hash key 最大偏移值, // 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_table_t中不存在要找的weak_entry_tuintptr_t max_hash_displacement;
};

这是一个全局弱引用hash表。使用不定类型对象的地址作为 key ,用 weak_entry_t 类型结构体对象作为 value 。其中的 weak_entries 成员,从字面意思上看,即为弱引用表入口,也就是weak指针集合的大门口。

中weak_entry_t是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个特定对象的所有弱引用集合。其定义如下:

typedef objc_object ** weak_referrer_t;
struct weak_entry_t {// 所有weak指针指向的特定对象DisguisedPtrobjc_object> referent;// 共用体,保存weak指针的集合, // 小于等于4个时为数组(下面的结构体), 超过4个时为hash表(上面的结构体)union {struct {weak_referrer_t *referrers;uintptr_t        out_of_line : 1;uintptr_t        num_refs : PTR_MINUS_1;uintptr_t        mask;uintptr_t        max_hash_displacement;};struct {// out_of_line=0 is LSB of one of these (don't care which)weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];};}
}

weak_entry_t 的结构中,DisguisedPtr referent对泛型对象的指针做了一个封装,通过这个泛型类来解决内存泄漏的问题。而且weak_entry_tweak_table_t内部都有一个hash表, 而且都是采用开放定值法解决的hash冲突, 从注释中写 out_of_line 成员为最低有效位,当其为1的时候, weak_referrer_t 成员将扩展为hash table。其中的 weak_referrer_t 是一个数组的别名。
那么在有效位生效的时候,out_of_line 、 num_refs、 mask 、 max_hash_displacement 有什么作用?

  • out_of_line:标志位。标志着weak_entry_t中是用数组保存还是hash表保存weak指针。
  • num_refs:引用数值。这里记录weak_entry_t表中weak指针的数量,
  • mask:weak_entry_t->referrers数组的count,
  • max_hash_displacement:hash key 最大偏移值, 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_entry_t中不存在要找的weak_entry_t。

其中 out_of_line 的值通常情况下是等于零的,所以弱引用表总是一个 objc_objective 指针数组,当超过4时, 会变成hash表。

总结一下 StripedMap[] : StripedMap 是一个模板类,在这个类中有一个 array 成员,用来存储 PaddedT 对象,并且其中对于 [] 符的重载定义中,会返回这个 PaddedT 的 value 成员,这个 value 就是我们传入的 T 泛型成员,也就是 SideTable 对象。 在 array 的下标中,这里使用了 indexForPointer 方法通过位运算计算下标,实现了静态的 Hash Table。而在 weak_table 中,其成员 weak_entry 会将传入对象的地址加以封装起来,并且其中也有访问全局弱引用表的入口。

weak的简述

weak表其实是一个hash表,Key是所指对象的地址,Value是weak指针的地址数组,weak是弱引用,所引用对象的计数器不会+1,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题。

weak的实现步骤

  1. 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  2. 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  3. 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

weak几个重要的实现函数

先写一个简单的例子:

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...NSObject *p = [[NSObject alloc] init];__weak NSObject *p1 = p;}return 0;
}

objc_initWeak 函数

初始化开始时,会调用 objc_initWeak 函数,初始化新的 weak 指针指向对象的地址。当我们初始化 weak 变量时,runtime 会调用 NSObject.mm 中的 objc_initWeak,而 objc_initWeak 函数里面的实现如下:

id objc_initWeak(id *location, id newObj) {// 查看对象实例是否有效,无效对象直接导致指针释放if (!newObj) {*location = nil;return nil;}// 这里传递了三个 Bool 数值// 使用 template 进行常量参数传递是为了优化性能return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>(location, (objc_object*)newObj);
}

然后我们看一下objc_initWeak()传入的两个参数代表什么:

  • location:__weak指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
  • newObj:所引用的对象。即例子中的p。

通过上面代码可以看出,objc_initWeak()函数首先判断指针指向的类对象是否有效,无效直接返回;否则通过 storeWeak() 被注册为一个指向 value 的 _weak 对象

objc_initWeak 函数里面会调用 objc_storeWeak() 函数,objc_storeWeak() 函数的作用是用来更新指针的指向,创建弱引用表。

objc_initWeak函数有一个前提条件:就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是nil,或者指向一个有效的对象。

objc_storeWeak()

// HaveOld:   true - 变量有值
//          false - 需要被及时清理,当前值可能为 nil
// HaveNew:  true - 需要被分配的新值,当前值可能为 nil
//          false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
//          false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {// 该过程用来更新弱引用指针的指向// 初始化 previouslyInitializedClass 指针Class previouslyInitializedClass = nil;id oldObj;// 声明两个 SideTable// ① 新旧散列创建SideTable *oldTable;SideTable *newTable;// 获得新值和旧值的锁存位置(用地址作为唯一标示)// 通过地址来建立索引标志,防止桶重复// 下面指向的操作会改变旧值retry:if (HaveOld) {// 更改指针,获得以 oldObj 为索引所存储的值地址oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}if (HaveNew) {// 更改新值指针,获得以 newObj 为索引所存储的值地址newTable = &SideTables()[newObj];} else {newTable = nil;}// 加锁操作,防止多线程中竞争冲突SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);// 避免线程冲突重处理// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改if (HaveOld  &&  *location != oldObj) {SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);goto retry;}// 防止弱引用间死锁// 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向if (HaveNew  &&  newObj) {// 获得新对象的 isa 指针Class cls = newObj->getIsa();// 判断 isa 非空且已经初始化if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) {// 解锁SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);// 对其 isa 指针进行初始化_class_initialize(_class_getNonMetaClass(cls, (id)newObj));// 如果该类已经完成执行 +initialize 方法是最理想情况// 如果该类 +initialize 在线程中 // 例如 +initialize 正在调用 storeWeak 方法// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记previouslyInitializedClass = cls;// 重新尝试goto retry;}}// ② 清除旧值if (HaveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// ③ 分配新值if (HaveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating);// 如果弱引用被释放 weak_register_no_lock 方法返回 nil // 在引用计数表中设置若引用标记位if (newObj  &&  !newObj->isTaggedPointer()) {// 弱引用位初始化操作// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用newObj->setWeaklyReferenced_nolock();}// 之前不要设置 location 对象,这里需要更改指针指向*location = (id)newObj;}else {// 没有新值,则无需更改}SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);return (id)newObj;
}

源码太长了,但是其实不难理解,

  • storeWeak方法实际上是接收了5个参数,分别是haveOldhaveNewcrashIfDeallocating这三个参数都是以模板的方式传入的,是三个bool类型的参数。分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
  • 该方法维护了oldTable和newTable分别表示旧的引用弱表和新的弱引用表,它们都是SideTable的hash表。
  • 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除。
  • 如果weak指针需要指向一个新的引用,则会调用weak_register_no_lock方法将新的weak指针地址添加到弱引用表中。
  • 调用setWeaklyReferenced_nolock方法修改weak新引用的对象的bit标志位

所以我们很容易就知道了这个方法重点也就是weak_unregister_no_lock和weak_register_no_lock这两个方法:

weak_register_no_lock方法添加弱引用

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;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_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中weak_entry_t *entry;if ((entry = weak_entry_for_referent(weak_table, referent))) { // 如果能找到weak_entry,则讲referrer插入到weak_entry中append_referrer(entry, referrer);     // 将referrer插入到weak_entry_t的引用数组中}else { // 如果找不到,就新建一个weak_entry_t new_entry(referent, referrer);weak_grow_maybe(weak_table);weak_entry_insert(weak_table, &new_entry);}// Do not set *referrer. objc_storeWeak() requires that the// value not change.return referent_id;
}

传入函数的四个参数分别代表什么:

- weak_table:weak_table_t结构类型的全局的弱引用表。

  • referent_id:weak指针。
  • *referrer_id:weak指针地址。
  • crashIfDeallocating :若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。

那么这个方法主要做了哪些工作呢:

  • 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作。
  • 如果对象正在析构,则抛出异常。
  • 如果对象不能被weak引用,直接返回nil。
  • 如果对象没有再析构且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry。

weak_entry_for_referent取元素

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{assert(referent);weak_entry_t *weak_entries = weak_table->weak_entries;if (!weak_entries) return nil;size_t begin = hash_pointer(referent) & weak_table->mask;  // 这里通过 & weak_table->mask的位操作,来确保index不会越界size_t index = begin;size_t hash_displacement = 0;while (weak_table->weak_entries[index].referent != referent) {index = (index+1) & weak_table->mask;if (index == begin) bad_weak_table(weak_table->weak_entries); // 触发bad weak table crashhash_displacement++;if (hash_displacement > weak_table->max_hash_displacement) { // 当hash冲突超过了可能的max hash 冲突时,说明元素没有在hash表中,返回nilreturn nil;}}return &weak_table->weak_entries[index];
}

append_referrer添加元素

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{if (! entry->out_of_line()) { // 如果weak_entry 尚未使用动态数组,走这里// Try to insert inline.for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {if (entry->inline_referrers[i] == nil) {entry->inline_referrers[i] = new_referrer;return;}}// 如果inline_referrers的位置已经存满了,则要转型为referrers,做动态数组。// Couldn't insert inline. Allocate out of line.weak_referrer_t *new_referrers = (weak_referrer_t *)calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));// This constructed table is invalid, but grow_refs_and_insert// will fix it and rehash it.for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {new_referrers[i] = entry->inline_referrers[I];}entry->referrers = new_referrers;entry->num_refs = WEAK_INLINE_COUNT;entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;entry->mask = WEAK_INLINE_COUNT-1;entry->max_hash_displacement = 0;}// 对于动态数组的附加处理:assert(entry->out_of_line()); // 断言:此时一定使用的动态数组if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍return grow_refs_and_insert(entry, new_referrer); // 扩容,并插入}// 如果不需要扩容,直接插入到weak_entry中// 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer// 细心的人可能注意到了,这里weak_entry_t 的hash算法和 weak_table_t的hash算法是一样的,同时扩容/减容的算法也是一样的size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 确保了 begin的位置只能大于或等于 数组的长度size_t index = begin;  // 初始的hash indexsize_t hash_displacement = 0;  // 用于记录hash冲突的次数,也就是hash再位移的次数while (entry->referrers[index] != nil) {hash_displacement++;index = (index+1) & entry->mask;  // index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)if (index == begin) bad_weak_table(entry); // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。}if (hash_displacement > entry->max_hash_displacement) { // 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置entry->max_hash_displacement = hash_displacement;}// 将ref存入hash数组,同时,更新元素个数num_refsweak_referrer_t &ref = entry->referrers[index];ref = new_referrer;entry->num_refs++;
}

weak_unregister_no_lock移除引用

如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除。

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,id *referrer_id)
{objc_object *referent = (objc_object *)referent_id;objc_object **referrer = (objc_object **)referrer_id;weak_entry_t *entry;if (!referent) return;if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找到referent所对应的weak_entry_tremove_referrer(entry, referrer);  // 在referent所对应的weak_entry_t的hash数组中,移除referrer// 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了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_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除weak_entry_remove(weak_table, entry);}}

那么这个函数都做了什么事情呢:

  • 首先,它会在weak_table中找出referent对应的weak_entry_t
  • weak_entry_t中移除referrer
  • 移除元素后,判断此时weak_entry_t中是否还有元素 (empty==true?)
  • 如果此时weak_entry_t已经没有元素了,则需要将weak_entry_tweak_table中移除

weak在释放时底层都做了哪些事情

我们学习了weak在初始化以及添加引用时都做了哪些事情,所以就剩最后一步了也就是释放:释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entryweak表中删除,最后清理对象的记录。

dealloc方法

当对象的引用计数为0时,底层会调用_objc_rootDealloc方法对对象进行释放,而在_objc_rootDealloc方法里面会调用rootDealloc方法。如下是rootDealloc方法的代码实现:

inline void
objc_object::rootDealloc()
{if (isTaggedPointer()) return;  // fixme necessary?if (fastpath(isa.nonpointer  &&!isa.weakly_referenced  &&!isa.has_assoc  &&!isa.has_cxx_dtor  &&!isa.has_sidetable_rc)){assert(!sidetable_present());free(this);}else {object_dispose((id)this);}
}
  • 首先判断对象是否是Tagged Pointer,如果是则直接返回。
  • 如果对象是采用了优化的isa计数方式,且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C++析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。
  • 如果不能满足上一条件,则会调用object_dispose方法。

object_dispose
object_dispose方法很简单,主要是内部调用了objc_destructInstance方法。

void *objc_destructInstance(id obj)
{if (obj) {// Read all of the flags at once for performance.bool cxx = obj->hasCxxDtor();bool assoc = obj->hasAssociatedObjects();// This order is important.if (cxx) object_cxxDestruct(obj);if (assoc) _object_remove_assocations(obj);obj->clearDeallocating();}return obj;
}

上面这一段代码很清晰,如果有自定义的C++析构方法,则调用C++析构函数。如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。

clearDeallocating

inline void
objc_object::clearDeallocating()
{if (slowpath(!isa.nonpointer)) {// Slow path for raw pointer isa.sidetable_clearDeallocating();}else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {// Slow path for non-pointer isa with weak refs and/or side table data.clearDeallocating_slow();}assert(!sidetable_present());
}

clearDeallocating中有两个分支,先判断对象是否采用了优化isa引用计数,如果没有的话则需要清理对象存储在SideTable中的引用计数数据。如果对象采用了优化isa引用计数,则判断是都有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合这两种情况中一种的,调用clearDeallocating_slow方法。

clearDeallocating_slow

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));SideTable& table = SideTables()[this]; // 在全局的SideTables中,以this指针为key,找到对应的SideTabletable.lock();if (isa.weakly_referenced) { // 如果obj被弱引用weak_clear_no_lock(&table.weak_table, (id)this); // 在SideTable的weak_table中对this进行清理工作}if (isa.has_sidetable_rc) { // 如果采用了SideTable做引用计数table.refcnts.erase(this); // 在SideTable的引用计数中移除this}table.unlock();
}

在这里我们关心的是weak_clear_no_lock方法。这里调用了weak_clear_no_lock来做weak_table的清理工作。

weak_clear_no_lock

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); // 找到referent在weak_table中对应的weak_entry_tif (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 referencesweak_referrer_t *referrers;size_t count;// 找出weak引用referent的weak 指针地址数组以及数组长度if (entry->out_of_line()) {referrers = entry->referrers;count = TABLE_SIZE(entry);}else {referrers = entry->inline_referrers;count = WEAK_INLINE_COUNT;}for (size_t i = 0; i < count; ++i) {objc_object **referrer = referrers[i]; // 取出每个weak ptr的地址if (referrer) {if (*referrer == referent) { // 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因*referrer = nil;}else if (*referrer) { // 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错_objc_inform("__weak variable at %p holds %p instead of %p. ""This is probably incorrect use of ""objc_storeWeak() and objc_loadWeak(). ""Break on objc_weak_error to debug.\n",referrer, (void*)*referrer, (void*)referent);objc_weak_error();}}}weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

  1. 调用objc_release
  2. 因为对象的引用计数为0,所以执行dealloc
  3. dealloc中,调用了_objc_rootDealloc函数
  4. _objc_rootDealloc中,调用了object_dispose函数
  5. 调用objc_destructInstance
  6. 最后调用objc_clear_deallocating

总结

  1. weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
  2. weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
  3. 对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

SideTableweak_table_tweak_entry_t这样三个结构,它们之间的关系如下图所示:

weak的底层原理流程图:

【iOS开发】——weak底层原理相关推荐

  1. iOS arc weak指针原理

    iOS arc weak指针原理 ARC 都帮我们做了什么? weak是什么? weak是怎么实现的? 1. weak原理简介 2. weak简单测试 3. weak原理分析 3.1 weak指针帮我 ...

  2. 海量数据去重,hash、布隆过滤器以及hyperloglog丨c/c++linux服务器开发丨后端开发丨Linux后台开发丨底层原理

    海量数据去重,hash.布隆过滤器以及hyperloglog 视频讲解如下,点击观看: 海量数据去重,hash.布隆过滤器以及hyperloglog丨c/c++linux服务器开发丨后端开发丨Linu ...

  3. redis,memcached到nginx,底层网络io中剥离精髓丨C/C++Linux丨C++后端开发丨Linux服务器开发丨底层原理

    redis,memcached到nginx,底层网络io中剥离精髓 1. redis单线程网络的优缺点 2. memcached多线程网络的并发优势 3. nginx多进程网络的优势 视频讲解如下,点 ...

  4. 浅析“分布式锁”的实现方式丨C++后端开发丨底层原理

    线程锁.进程锁以及分布式锁相关视频讲解:详解线程锁.进程锁以及分布式锁 如何高效学习使用redis相关视频讲解:10年大厂程序员是如何高效学习使用redis Linux服务器开发高级架构学习视频:C/ ...

  5. iOS进阶之底层原理-weak实现原理

    基本上每一个面试都会问weak的实现原理,还有循环引用时候用到weak,今天我们就来研究下weak的实现原理到底是什么. weak入口 我们在这里打个断点,然后进入汇编调试. 这里就很明显看到了入口, ...

  6. 如何接入微信公众号开发?底层原理是什么?

    要接入微信公众号开发,您需要完成以下几个步骤: 注册微信公众平台账号:首先,您需要在微信公众平台上注册一个账号,并创建一个公众号.在注册过程中,您需要提供相关的身份信息和认证材料,以便微信审核和认证您 ...

  7. iOS 进阶之底层原理一OC对象原理alloc做了什么

    人狠话不多,直接上干货.这是第一篇,之后还会持续更新,当作自己学习的笔记,也同时分享给大家,希望帮助更多人. 首先,我们来思考,下面这段代码的输出是否相同.答案很明显,p1.p2.p3是指向相同的对象 ...

  8. iOS进阶之底层原理-block本质、block的签名、__block、如何避免循环引用

    面试的时候,经常会问到block,学完本篇文章,搞通底层block的实现,那么都不是问题了. block的源码是在libclosure中. 我们带着问题来解析源码: blcok的本质是什么 block ...

  9. iOS进阶之底层原理-线程与进程、gcd

    线程与进程 线程的定义 线程是进程的基本单位,一个进程的所有任务都在线程中执行 进程要想执行任务,必须的有线程,进程至少要有一条线程 程序启动默认会开启一条线程,也就是我们的主线程 进程的定义 进程是 ...

  10. iOS进阶之底层原理-应用程序加载(dyld加载流程、类与分类的加载)

    iOS应用程序的入口是main函数,那么main函数之前系统做了什么呢? 我们定义一个类方法load,打断点,查看栈进程,我们发现dyld做了很多事,接下来就来探究到底dyld做了什么. 什么是dyl ...

最新文章

  1. linux shell命令行及脚本编程实例详解_Linux高手必看的10本经典书籍
  2. PowerShell CLI 获取VM信息
  3. CMake2:版本号配置与头文件生成
  4. C++ 字符数组函数与string函数
  5. 回溯法基本思想_LeetCode--回溯法心得
  6. Java 的单例模式
  7. java forkjoin MySQL_Java并发fork-join框架
  8. Esxi直通板载Sata
  9. Android 开发之旅:深入分析布局文件又是“Hello World!”
  10. 简单的Postman,硬是玩出花!我能咋办
  11. 【Flink】Flink + Drools 构建规则模型
  12. 从此,激光雷达和摄像头,就是一个东西了?
  13. 【UML建模案例】小型网上书店系统
  14. Spring bean生命周期详解
  15. NTC热敏电阻原理及应用详解
  16. Autosar OSEK 网络管理学习笔记
  17. win10电脑双屏如何设置不同的桌面
  18. jquery发送ajax请求并设置请求头
  19. 鼠标坏了怎么用键盘操作鼠标
  20. 搭建邮件群发服务器费用,自建邮件群发服务器优缺点分析

热门文章

  1. SecureCRT快捷键大全
  2. 某大型软件公司售前软件工程师面试题附答案
  3. 2021年起重机械指挥考试题库及起重机械指挥最新解析
  4. xenu工具如何扫描网站
  5. 融入动画技术的交互应用——简单弹幕游戏
  6. Roaring Bitmaps结构原理
  7. 如何在Mysql中运行SQL文件
  8. Pandas数据分析实战(1)——探索Chipotle快餐数据
  9. NEO dapp开发系列课程 第一组 第二讲
  10. 苹果支付Java后台总结