关键字weak实现原理,iOS开发之weak底层实现原理

在iOS开垦进度中,会时时选拔到贰个修饰词“weak”,使用情形大家都相比较明晰,用于一些目的相互援用的时候,防止现身强强引用,对象无法被释放,出现内部存款和储蓄器走漏的标题。

图片 1

只要学过 iOS 的人,都会对 strong、weak、copy等重大字应该都会很熟知。weak 属性关键字就是弱引用,它不会大增援用计数但却能确认保证指针的平安访谈,在对象释放前置为 nil,进而制止不当的内部存款和储蓄器访谈。主要为了化解循环引用的主题材料。

weak 关键字的功力弱援用,所引用对象的计数器不会加一,并在引用对象被假释的时候自动被设置为 nil。

原文
很稀少人精晓weak表其实是三个hash(哈希)表,Key是所指对象的地点,Value是weak指针的地方数组。更五人的人只是驾驭weak是弱援用,所援用对象的计数器不会加一,并在援用对象被保释的时候自动被设置为nil。常常用于缓慢解决循环援引难点。但现行反革命单知道这么些曾经不足以应对面试了,好些个公司会问weak的原理。weak的原理是什么样呢?上边就剖判一下weak的专门的学业规律(只是自身对那么些标题奇异,学习进程中的笔记,希望对读者也颇有补助)。

接下去,大家会从 objc 库中的 NSObject.mm、 objc-weak.h 乃至 objc-weak.mm 文件出发,去具体精通 weak 的完成进程。

作者给我们推荐二个iOS手艺调换群:923910776!群内提供数据结构与算法、底层进级、swift、逆向、整合面试题等无需付费资料!

weak 完成原理的席卷

Runtime维护了一个weak表,用于存款和储蓄指向有些对象的具有weak指针。weak表其实是贰个hash(哈希)表,Key是所指对象的位置,Value是weak指针的地点(这几个地点的值是所指对象指针的地点)数组。

weak 的内部结构

Runtime 维护了二个weak表,用于存款和储蓄指向某些对象的享有weak指针。weak 表是由单个自旋八爪鱼理的散列表。weak表其实是三个hash表,key 是所指对象的指针,value是weak指针的地址(这些地址的值是所针对对象的地点)数组。

在底下涉及的源码中,我们拜会到以下几个门类:sideTableweak_table_tweak_entry_t 那多少个结构体。

struct SideTable { // 自旋锁,用来保证线程安全 spinlock_t slock; // 引用计数表 RefcountMap refcnts; // weak 表 weak_table_t weak_table; ...};

SideTable,它用来治本援用计数表和 weak 表,并应用 spinlock_lock 自旋锁来严防操作表结构时也许的竞态条件。它用三个 64*128 大小的uint8_t 静态数组作为 buffer 来保存全部的 SideTable 实例。这么些结构体里面含有八个变量,第叁个spinlock_t,它是叁个自旋锁,用来保管线程安全。第4个RefcountMap,是援引计数表,种种对象的引用计数保存在大局的援引计数表中,一个对象地址对应一个引用计数。第多个就是大家接下去要讲的 weak 表,全部的 weak 变量会被参预到全局的weak表中,表的 key 是 weak 修饰的变量指向的目的, value 值正是 weak 修饰的变量。接下来,大家现实看看那些 weak 表

struct weak_table_t { // 保存了所有指向指定对象的 weak 指针 weak_entry_t *weak_entries; // 存储空间,即 entries 的数目 size_t num_entries; // 参与判断引用计数辅助量 uintptr_t mask; // hash key 最大偏移量 uintptr_t max_hash_displacement;};

以此是全局弱引用的 hash 表。它的功力就是在目的施行 dealloc 的时候将兼具指向该对象的 weak 指针的值设为 nil, 防止悬空指针。它接纳不定类型对象的地方的 hash 化后的数值作为 key,用 weak_entry_t 类型的结构体对象作为 value。个中 weak_entry_t 是存款和储蓄在弱援用表中的二个里面结构体,它承受掩护和仓库储存指向二个对象的兼具弱引用hash 表。其定义如下:

// 存储在弱引用表中的一个内部结构体#define WEAK_INLINE_COUNT 4struct weak_entry_t { DisguisedPtr<objc_object> referent; // 封装 objc_object 指针,即 weak 修饰的变量指向的对象 union { struct { weak_referrer_t *referrers; uintptr_t out_of_line : 1; // LSB 最低有效元 当标志位为0时,增加引用表指针纬度, // 当其为0的时候, weak_referrer_t 成员将扩展为静态数组型的 hash table uintptr_t num_refs : PTR_MINUS_1; // 引用数值,这里记录弱引用表中引用有效数字,即里面元素的数量 uintptr_t mask; uintptr_t max_hash_displacement; // hash 元素上限阀值 }; 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<objc_object> 是对 objc_object * 指针及其一些操作进行的卷入,目标就是为着让它给人看起来不会有内部存款和储蓄器走漏的样板,其剧情可以见到为对象的内存地址。out_of-line 成员为最低有效位,当其为 0 的时候,weak_referrer_t 成员将扩充为多个静态数组型的 hash table。其实 weak_referrer 是objc_objcet 的别称,定义如下:typedef objc_object ** weak_referrer_t;

它经过三个二维指针地址偏移,用下标作为 hash 的 key,做成了一个弱援引散列。

下图,就是weak表结构的下结论:[图片上传失利...(image-149304-1524568324284)]

种种对象的 SideTable 中的 weak_table_t 都以大局 weak 表的进口,以援用计数对象为键找到其所记录的 weak 修饰的对象。weak_entry_t 中的 referrers 有二种方式,当 out_of_line 为 0 的时候,referrers 是二个静态数组型的表,数组大小默认为WEAK_INLINE_COUNT 大小,当 out_of_line 不为 0 的时候,referrers 是二个动态数组,内容随之扩展。

率先须求看一下weak编写翻译之后实际出现哪些的调换,通过Clang的点子把weak编写翻译成C

weak 的贯彻原理可以饱含一下三步:

1、伊始化时:runtime会调用objc_initWeak函数,开端化三个新的weak指针指向对象的地方。
2、加多引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是翻新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先依照目的地址获取具备weak指针地址的数组,然后遍历那一个数组把在那之中的数目设为nil,最终把这些entry从weak表中删去,最终清理对象的笔录。

上面将上马详细介绍每一步:

weak 完毕原理的进度

当大家用 weak 修饰属性的时候,它是怎么落到实处当所引述的对象被抛弃的时候,变量置为 nil,大家来索求一下。

{ id obj1 = [[NSObject alloc] init]; id __weak obj2 = obj1;}

经过编写翻译期调换之后,以上代码会化为上面那样

id obj2;objc_initWeak(&obj2, obj1);objc_destroyWeak(&obj2);

大家开掘,weak 修饰符变量是经过 objc_initWeak 函数来开始化的,在变量功效域停止的时候经过 objc_destroyWeak 函数来刑满释放解除劳教该变量的。接下来,大家看看那八个函数的源码。

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

void objc_destroyWeak(id *location){ storeWeak<true/*old*/, false/*new*/, false/*crash*/> (location, nil);}

对那八个办法的分析后,大家发掘它们都调用了storeWeak 这些函数,可是八个主意传入的参数却稍有例外。init 方法中,第五个参数为 weak 修饰的变量,第4个参数为引用计数对象。但在 destoryWeak 函数,第一参数依然为 weak 修饰的变量,第4个参数为 nil。那那块传入分裂的参数到底意味着怎么着,大家继续深入分析 storeWeak 那几个函数。

// 更新一个弱引用变量// 如果 HaveOld 是 true, 变量是个有效值,需要被及时清理。变量可以为 nil。// 如果 HaveNew 是 true, 需要一个新的 value 来替换变量。变量可以为 nil// 如果crashifdeallocation 是 ture ,那么如果 newObj 是 deallocating,或者 newObj 的类不支持弱引用,则该进程就会停止。// 如果crashifdeallocation 是 false,那么 nil 会被存储。template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>static id storeWeak(id *location, objc_object *newObj){ assert(HaveOld || HaveNew); if  assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; // 创建新旧散列表 SideTable *oldTable; SideTable *newTable; // Acquire locks for old and new values. // 获得新值和旧值的锁存位置 (用地址作为唯一标示) // Order by lock address to prevent lock ordering problems. // 通过地址来建立索引标志,防止桶重复 // Retry if the old value changes underneath us. // 下面指向的操作会改变旧值 retry: if  { // 如果 HaveOld 为 true ,更改指针,获得以 oldObj 为索引所存储的值地址 oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if  { // 获得以 newObj 为索引所存储的值对象 newTable = &SideTables()[newObj]; } else { newTable = nil; } // 对两个 table 进行加锁操作,防止多线程中竞争冲突 SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改, 保证线程安全,这个判断用来避免线程冲突重处理问题 if (HaveOld && *location != oldObj) { SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the  initialize machinery by ensuring that no // weakly-referenced object has an un- initialized isa. // 防止弱引用之间发生死锁,并且通过  initialize 初始化构造器保证所有弱引用的 isa 非空指向 if (HaveNew && newObj) { // 获得新对象的 isa 指针 Class cls = newObj->getIsa(); // 判断 isa 非空且已经初始化 if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized { // 对两个表解锁 SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClassnewObj)); // If this class is finished with  initialize then we're good. // If this class is still running  initialize on this thread // (i.e.  initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. // 如果该类已经完成执行  initialize 方法是最好的,如果该类   initialize 在线程中,例如  initialize 正在调用storeWeak 方法,那么则需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记然后重新尝试 previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. 清除旧值 if  { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. 分配新值 if  { newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, newObj, location, CrashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // 如果弱引用被释放则该方法返回 nil // Set is-weakly-referenced bit in refcount table. // 在引用计数表中设置弱引用标记位 if (newObj && !newObj->isTaggedPointer { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); return newObj;}

如上就是 store_weak 这一个函数的贯彻,它至关主要做了以下几件事:

  • 声明了新旧散列表指针,因为 weak 修饰的变量若是从前曾经针对性三个指标,然后其再度更换指向另四个对象,那么按理来讲我们必要自由旧目的中该 weak 变量的记录,也正是要将旧记录删除,然后在新记录中充裕。这里的新旧散列表正是那么些职能。
  • 基于新旧变量的地址获取相应的 SideTable
  • 对七个表张开加锁操作,防止十六线程竞争冲突
  • 举行线程冲突重处理判别
  • 认清其 isa 是还是不是为空,为空则须要开展发轫化
  • 举例存在旧值,调用 weak_unregister_no_lock 函数清除旧值
  • 调用 weak_register_no_lock 函数分配新值
  • 解锁五个表,并重回第二参数

伊始化弱引用对象流程一览

弱援用的起首化,从上文的分析能够看出,首要的操作部分便是在弱引用表的取键、查询散列、创立弱引用等操作,能够总括出如下的流程图:

图片 2image

旧目的解除注册操作 weak_unregister_no_lock

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  { weak_entry_remove(weak_table, entry); } } // Do not set *referrer = nil. objc_storeWeak() requires that the // value not change.}

该办法首要成效是将旧目的在 weak_table 中接触 weak 指针的应和绑定。依照函数名,称之为解除注册操作。来探视这么些函数的逻辑。首先参数是 weak_table_t 表,键和值。声明 weak_entry_t 变量,倘若key,也便是援用计数对象为空,直接回到。依照全局入口表和键获取相应的 weak_entry_t 对象,也正是 weak 表记录。获取到记录后,将记录表以至 weak 对象作为参数字传送入 remove_referrer 函数中,那几个函数就是破除操作。然后决断那一个 weak 记录是不是为空,如若为空,从大局记录表中排除相应的援引计数对象的 weak 记录表。

接下去,大家精晓一下,如何拿到那几个 weak_entry_t 这么些变量。

static weak_entry_t *weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent){ assert; weak_entry_t *weak_entries = weak_table->weak_entries; if (!weak_entries) return nil; size_t index = hash_pointer & weak_table->mask; size_t hash_displacement = 0; while (weak_table->weak_entries[index].referent != referent) { index =  & weak_table->mask; hash_displacement  ; if (hash_displacement > weak_table->max_hash_displacement) { return nil; } } return &weak_table->weak_entries[index];}

那么些函数的逻辑便是先获得全局 weak 表入口,然后将引用计数对象的地点进行hash 化后与 weak_table->mask 做与操作,作为下标,在全局 weak 表中探索,若找到,重返那个目的的 weak 记录表,若未有,重临nil。

再来精晓一下拔除对象的函数:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer){ if (! entry->out_of_line) { for (size_t i = 0; i < WEAK_INLINE_COUNT; i  ) { if (entry->inline_referrers[i] == old_referrer) { entry->inline_referrers[i] = nil; return; } } _objc_inform("Attempted to unregister unknown __weak variable " "at %p. This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.n", old_referrer); objc_weak_error(); return; } size_t index = w_hash_pointer(old_referrer) & (entry->mask); size_t hash_displacement = 0; while (entry->referrers[index] != old_referrer) { index =  & entry->mask; 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] = nil; entry->num_refs--;}

其一函数字传送入的是 weak 对象,当 out_of_line 为0 时,遍历数组,找到相应的靶子,置nil,借使未找到,报错并再次回到。当 out_of_line 不为0时,依照目的的地址 hash 化并和 mask 做与操作作为下标,查找相应的对象,若未有,报错并再次来到,若有,相应的置为 nil,并缩减成分数量,即 num_refs 减 1。

新对象增多注册操作 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; 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) = (objc_object *, SEL)) object_getMethodImplementationreferent, SEL_allowsWeakReference); if allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance  of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", referent, object_getClassNamereferent)); } 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; new_entry.referent = referent; new_entry.out_of_line = 0; new_entry.inline_referrers[0] = referrer; for (size_t i = 1; i < WEAK_INLINE_COUNT; i  ) { new_entry.inline_referrers[i] = nil; } 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-else, 主假如为着推断该对象是否 taggedPoint 以至是或不是正在调用 dealloca 等。上面操作起来,同样是先得到 weak 表记录,即便得到到,则调用 append_referrer 插入对象,若未有,则新建一个 weak 表记录,默以为out_of_line,然后将新对象放置 0 下标地点,别的职务置为 nil 。上边多少个函数 weak_grow_maybe 是用来决断是还是不是需求再行请内部存款和储蓄重视hash,weak_entry_insert 函数是用来将新建的 weak 表记录插入到全局 weak 表中。插入时同样是以目的地址的 hash 化和 mask 值相与作为下标来记录的。

接下去看看 append_referrer 函数,源代码如下:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer){ if (! entry->out_of_line) { // Try to insert inline. for (size_t i = 0; i < WEAK_INLINE_COUNT; i  ) { if (entry->inline_referrers[i] == nil) { entry->inline_referrers[i] = new_referrer; return; } } // Couldn't insert inline. Allocate out of line. 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 = 1; entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } assert(entry->out_of_line); if (entry->num_refs >= TABLE_SIZE * 3/4) { return grow_refs_and_insert(entry, new_referrer); } size_t index = w_hash_pointer(new_referrer) & (entry->mask); size_t hash_displacement = 0; while (entry->referrers[index] != NULL) { index =  & entry->mask; hash_displacement  ; } 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  ;}

当 out_of_line 为 0,况且静态数组里面还应该有位置寄存,那么直接存放并重临。若无地方贮存,则升高为动态数组,并插足。假使out_of_line 不为 0,先判别是不是须要扩大容积,然后同样的,使用对象地址的 hash 化和 mask 做与操作作为下标,找到呼应的职分并插入。

指标的销毁乃至 weak 的置 nil 完毕

释放时,调用clearDeallocating函数。clearDeallocating 函数首先遵照目的地址获取具备weak指针地址的数组,然后遍历那一个数组把里面包车型客车数量设为nil,最后把这些entry从weak表中剔除,最终清理对象的笔录。

当weak引用指向的指标被假释时,又是哪些去管理weak指针的吧?当释放对象时,其核心流程如下:

  • 调用 objc_release
  • 因为对象的援引计数为0,所以举办dealloc
  • 在dealloc 中,调用了_objc_rootDealloc 函数
  • 在 _objc_rootDealloc 中,调用了 objec_dispose 函数
  • 调用objc_destructInstance
  • 末段调用 objc_clear_deallocating

objc_clear_deallocating的切切实实贯彻如下:

void objc_clear_deallocating { assert; assert; if (obj->isTaggedPointer return; obj->clearDeallocating();}

以此函数只是做一些推断以致更加深档期的顺序的函数调用,

void objc_object::sidetable_clearDeallocating(){ SideTable& table = SideTables()[this]; // clear any weak table items // clear extra retain count and deallocating bit // (fixme warn or abort if extra retain count == 0 ?) table.lock(); // 迭代器 RefcountMap::iterator it = table.refcnts.find; if (it != table.refcnts.end { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table.weak_table, ; } table.refcnts.erase; } table.unlock();}

咱俩得以看到,在此个函数中,首先收取对象对应的SideTable实例,假若那几个目的有关联的弱援引,则调用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); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %pn", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line) { referrers = entry->referrers; count = TABLE_SIZE; } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count;   i) { objc_object **referrer = referrers[I]; if  { 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, *referrer, referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry);}

本条函数根据 out_of_line 的值,获得相应的记录表,然后根据援引计数对象,将相应的 weak 对象置 nil。末了打消相应的记录表。

经过地点的叙说,大家着力能理解二个weak引用从生到死的历程。从这么些流程可以看出,二个weak援引的拍卖涉及各样查表、增多与删除操作,仍然有自然消耗的。所以要是大度选取__weak变量的话,会对品质变成一定的震慑。那么,我们相应在如几时候去选用weak呢?《Objective-C高等编制程序》给我们的建议是只在防止循环援用的时候使用__weak修饰符。

int main(){ NSObject *obj = [[NSObject alloc] init]; id __weak obj1 = obj;}
1、伊始化时:runtime会调用objc_initWeak函数,objc_initWeak函数会初阶化叁个新的weak指针指向对象的地点。

演示代码:

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}

当我们伊始化贰个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。这么些函数在Clang中的注脚如下:

id objc_initWeak(id *object, id value);

而对于 objc_initWeak() 方法的贯彻

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

可以看到,那一个函数仅仅是三个深层函数的调用入口,而相似的入口函数中,都会做一些粗略的剖断(比方objc_msgSend 中的缓存判定),这里剖断了其指针指向的类对象是不是行得通,无效直接出狱,不再往深层调用函数。否则,object将被注册为一个针对性value的__weak对象。而那件事应该是objc_storeWeak函数干的。

注意:objc_initWeak函数有二个前提条件:就是object必得是一个并未有被注册为__weak对象的一蹴而就指针。而value则能够是null,或许指向叁个立竿见影的指标。

编写翻译之后的weak,通过objc_ownership实现weak方法,objc_ownership字面意思是:获得对象的全数权,是对目的weak的开始化的八个操作。

2、增加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的功力是革新指针指向,创立对应的弱引用表。

objc_storeWeak的函数表明如下:

id objc_storeWeak(id *location, id value);

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;
}

撇开源码中各个锁操作,来拜会这段代码都做了些什么。

在选择clang编写翻译进程中会报错误,使用下方的措施编码编写翻译现身errorclang -rewrite-objc -fobjc-arc -stdlib=libc -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m

1)、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 全局 hash 表的布局与效用。

int main(){ NSObject *obj = ((NSObject *objc_msgSend)((NSObject *objc_msgSend)objc_getClass("NSObject"), sel_registerName, sel_registerName; id __attribute__((objc_ownership obj1 = obj;}
2)、weak表

weak表是八个弱援引表,实现为贰个weak_table_t结构体,存储了有个别对象相关的的具有的弱引用音信。其定义如下(具体定义在objc-weak.h中):

struct weak_table_t {
    // 保存了所有指向指定对象的 weak 指针
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

那是贰个大局弱引用hash表。使用不定类型对象的地点作为 key ,用 weak_entry_t 类型结构体对象作为 value 。个中的 weak_entries 成员,从字面意思上看,即为弱援引表入口。其实现也是那般的。

其中weak_entry_t是存款和储蓄在弱援用表中的二个内部结构体,它担任维护和存储指向二个指标的具有弱援引hash表。其定义如下:

typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtrobjc_object> referent;
    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 是对泛型对象的指针做了贰个装进,通过这么些泛型类来解决内部存款和储蓄器泄漏的主题材料。从注释中写 out_of_line 成员为压低有效位,当其为0的时候, weak_referrer_t 成员将扩张为多行静态 hash table。其实里面包车型大巴 weak_referrer_t 是二维 objc_object 的外号,通过三个二维指针地址偏移,用下标作为 hash 的 key,做成了八个弱引用散列。
那么在有效位未奏效的时候,out_of_line 、 num_refs、 mask 、 max_hash_displacement 有啥意义?以下是作者自己的揣度:

out_of_line:最低有效位,也是注解位。当标识位 0 时,扩展援引表指针纬度。
num_refs:援引数值。这里记录弱引用表中援引有效数字,因为弱援引表使用的是静态 hash 结构,所以须求动用变量来记录数据。
mask:计数帮忙量。
max_hash_displacement:hash 成分上限阀值。
其实 out_of_line 的值平常状态下是等于零的,所以弱援引表总是二个objc_objective 指针二维数组。一维 objc_objective 指针可组合一张弱引用散列表,通过第三纬度实现了多张散列表,而且表数据为 WEAK_INLINE_COUNT 。

小结一下 StripedMap[] : StripedMap 是一个模板类,在此个类中有叁个array 成员,用来囤积 PaddedT 对象,何况个中对于 [] 符的重载定义中,会回来那些 PaddedT 的 value 成员,这几个 value 正是大家传入的 T 泛型成员,也正是 SideTable 对象。在 array 的下标中,这里运用了 indexForPointer 方法通过位运算总计下标,完毕了静态的 Hash Table。而在 weak_table 中,其成员 weak_entry 会将盛传对象的地点加以封装起来,并且当中也可能有访谈全局弱援用表的进口。

图片 3

第一、通过weak编写翻译分析,能够看出来weak通过runtime初步化的并珍爱的;第二、weak和strong都以Object-C的修饰词,而strong是经过runtime维护的多个自行计数表结构。综上:weak是有Runtime维护的weak表。

旧指标解除注册操作 weak_unregister_no_lock

该格局首要意义是将旧指标在 weak_table 中接触 weak 指针的应和绑定。依照函数名,称之为解除注册操作。从源码中,能够清楚其坚守正是从 weak_table 中接触 weak 指针的绑定。而里边的遍历查询,正是本着于 weak_entry 中的多张弱援引散列表。

在runtime源码中,可以找到’objc-weak.h’和‘objc-weak.mm’文件,并且在objc-weak.h文件中关于定义weak表的结构体乃至有关的不二秘技。

新对象增添注册操作 weak_register_no_lock

这一步与上一步相反,通过 weak_register_no_lock 函数把心的指标举行挂号操作,实现与相应的弱援用表实行绑定操作。

2.1.weak表weak_table_t是七个大局weak 援引的表,使用不定类型对象的地址作为 key,用 weak_entry_t 类型结构体对象作为 value 。个中的 weak_entries 成员

初叶化弱引用对象流程一览

弱引用的最先化,从上文的剖判中能够看来,首要的操作部分就在弱援用表的取键、查询散列、创建弱援用表等操作,能够总计出如下的流程图:

图片 4

以此图中简易了重重景色的论断,可是当声飞鹤(Beingmate)个 __weak 会调用上海体育地方中的那么些办法。当然, storeWeak 方法不止用在 __weak 的扬言中,在 class 内部的操作中也会时常通过该方法来对 weak 对象进行操作。

/** * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */struct weak_table_t { weak_entry_t *weak_entries; //保存了所有指向指定对象的weak指针 weak_entries的对象 size_t num_entries; // weak对象的存储空间 uintptr_t mask; //参与判断引用计数辅助量 uintptr_t max_hash_displacement; //hash key 最大偏移值};
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先遵照目的地址获取具有weak指针地址的数组,然后遍历那些数组把当中的数据设为nil,最终把这一个entry从weak表中去除,最终清理对象的笔录。

当weak援引指向的对象被放出时,又是何等去管理weak指针的吧?当释放对象时,其核心流程如下:

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

重要看对象被释放时调用的objc_clear_deallocating函数。该函数达成如下:

void  objc_clear_deallocating(id obj) 
{
    assert(obj);
    assert(!UseGC);
    if (obj->isTaggedPointer()) return;
   obj->clearDeallocating();
}

也正是调用了clearDeallocating,继续追踪能够开采,它最终是使用了迭代器来取weak表的value,然后调用weak_clear_no_lock,然后找出对应的value,将该weak指针置空,weak_clear_no_lock函数的落到实处如下:

/**
 * 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 %pn", 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) {
            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);
}

objc_clear_deallocating该函数的动作如下:

1、从weak表中赢得抛弃对象的地方为键值的记录
2、将饱含在笔录中的全部附有 weak修饰符变量的地址,赋值为nil
3、将weak表中该记录删除
4、从引用计数表中除去抛弃对象的地方为键值的记录

看了objc-weak.mm的源码就清楚了:其实Weak表是多个hash(哈希)表,然后中间的key是指向指标的地方,Value是Weak指针的地址的数组。

weak全局表中的寄放weak定义的指标的表结构weak_entry_t,weak_entry_t是存储在弱援用表中的八个之中结构体,它承受维护和累积指向一个对象的具备弱援引hash表。其定义如下:

补充:.m和.mm的区别

.m:源代码文件,那么些独立的源代码文件增加名,可以包含OC和C代码。
.mm:源代码文件,带有这种扩充名的源代码文件,除了能够分包OC和C代码之外,还足以包涵C 代码。仅在您的OC代码中确实供给使用C 类或许个性的时候才用这种扩张名。

参照他事他说加以考察资料:
weak 弱引用的兑现格局
weak的生命周期:具体落到实处况势

typedef objc_object ** weak_referrer_t;struct weak_entry_t { DisguisedPtr<objc_object> referent; //范型 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 是对泛型对象的指针做了贰个包裹,通过这些泛型类来解决内部存款和储蓄器泄漏的标题。从注释中写 out_of_line 成员为压低有效位,当其为0的时候, weak_referrer_t 成员将扩展为多行静态 hash table。其实此中的 weak_referrer_t 是二维 objc_object 的外号,通过一个二维指针地址偏移,用下标作为 hash 的 key,做成了四个弱引用散列。out_of_line:最低有效位,也是标识位。当标识位 0 时,扩充援用表指针纬度。

num_refs:征引数值。这里记录弱援引表中引用有效数字,因为弱引用表使用的是静态 hash 结构,所以要求选用变量来记录数据。mask:计数协助量。max_hash_displacement:hash 成分上限阀值。其实 out_of_line 的值日常情状下是等于零的,所以弱引用表总是三个 objc_objective 指针二维数组。一维 objc_objective 指针可组成一张弱援引散列表,通过第三纬度完毕了多张散列表,並且表数据为 WEAK_INLINE_COUNT 。

objc_object是weak_entry_t表中weak弱援引对象的范型对象的结构体结构。

struct objc_object {private: isa_t isa;public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); // initIsa() should be used to init the isa of new objects only. // If this object already has an isa, use changeIsa() for correctness. // initInstanceIsa(): objects with no custom RR/AWZ // initClassIsa(): class objects // initProtocolIsa(): protocol objects // initIsa(): other objects void initIsa(Class cls /*indexed=false*/); void initClassIsa(Class cls /*indexed=maybe*/); void initProtocolIsa(Class cls /*indexed=maybe*/); void initInstanceIsa(Class cls, bool hasCxxDtor); // changeIsa() should be used to change the isa of existing objects. // If this is a new object, use initIsa() for performance. Class changeIsa(Class newCls); bool hasIndexedIsa(); bool isTaggedPointer(); bool isClass(); // object may have associated objects? bool hasAssociatedObjects(); void setHasAssociatedObjects(); // object may be weakly referenced? bool isWeaklyReferenced(); void setWeaklyReferenced_nolock(); // object may have -.cxx_destruct implementation? bool hasCxxDtor(); // Optimized calls to retain/release methods id retain(); void release(); id autorelease(); // Implementations of retain/release methods id rootRetain(); bool rootRelease(); id rootAutorelease(); bool rootTryRetain(); bool rootReleaseShouldDealloc(); uintptr_t rootRetainCount(); // Implementation of dealloc methods bool rootIsDeallocating(); void clearDeallocating(); void rootDealloc();private: void initIsa(Class newCls, bool indexed, bool hasCxxDtor); // Slow paths for inline control id rootAutorelease2(); bool overrelease_error();#if SUPPORT_NONPOINTER_ISA // Unified retain count manipulation for nonpointer isa id rootRetain(bool tryRetain, bool handleOverflow); bool rootRelease(bool performDealloc, bool handleUnderflow); id rootRetain_overflow(bool tryRetain); bool rootRelease_underflow(bool performDealloc); void clearDeallocating_weak(); // Side table retain count overflow for nonpointer isa void sidetable_lock(); void sidetable_unlock(); void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced); bool sidetable_addExtraRC_nolock(size_t delta_rc); bool sidetable_subExtraRC_nolock(size_t delta_rc); size_t sidetable_getExtraRC_nolock();#endif // Side-table-only retain count bool sidetable_isDeallocating(); void sidetable_clearDeallocating(); bool sidetable_isWeaklyReferenced(); void sidetable_setWeaklyReferenced_nolock(); id sidetable_retain(); id sidetable_retain_slow(SideTable *table); bool sidetable_release(bool performDealloc = true); bool sidetable_release_slow(SideTable *table, bool performDealloc = true); bool sidetable_tryRetain(); uintptr_t sidetable_retainCount();#if !NDEBUG bool sidetable_present();#endif};

总之:1.weak_table_t:选择hash的点子把具备weak引用的靶子,存储全体援引weak对象2.weak_entry_t(weak_table_t表中hash表的value值,weak对象体):用于记录hash表中weak对象3.objc_object(weak_entry_t对象中的范型对象,用于标识对象weak对象):用于标示weak援引的对象。

详细讲明weak存款和储蓄对象协会,对接下去对weak操作使用能够更进一竿清晰的敞亮weak的应用。

2.2.weak底层完毕原理在runtime源码中的NSObject.mm文件中找到了关于早先化和管制weak表的法子

  • 初始化weak表方法
//初始化weak表/** * Initialize a fresh weak pointer to some object location. * It would be used for code like: * * (The nil case) * __weak id weakPtr; * (The non-nil case) * NSObject *o = ...; * __weak id weakPtr = o; * * @param addr Address of __weak ptr. * @param val Object ptr. */id objc_initWeak(id *addr, id val){ *addr = 0; if  return nil; return objc_storeWeak(addr, val); // 存储weak对象}
  • 积攒weak对象的点子
 * This function stores a new value into a __weak variable. It would * be used anywhere a __weak variable is the target of an assignment. * * @param location The address of the weak pointer itself * @param newObj The new object this weak ptr should now point to * * @return e newObj */idobjc_storeWeak(id *location, id newObj){ id oldObj; SideTable *oldTable; SideTable *newTable; spinlock_t *lock1;#if SIDE_TABLE_STRIPE > 1 spinlock_t *lock2;#endif // Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: oldObj = *location; oldTable = SideTable::tableForPointer; newTable = SideTable::tableForPointer; lock1 = &newTable->slock;#if SIDE_TABLE_STRIPE > 1 lock2 = &oldTable->slock; if (lock1 > lock2) { spinlock_t *temp = lock1; lock1 = lock2; lock2 = temp; } if (lock1 != lock2) spinlock_lock;#endif spinlock_lock; if (*location != oldObj) { spinlock_unlock;#if SIDE_TABLE_STRIPE > 1 if (lock1 != lock2) spinlock_unlock;#endif goto retry; } weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); newObj = weak_register_no_lock(&newTable->weak_table, newObj, location); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = newObj; spinlock_unlock;#if SIDE_TABLE_STRIPE > 1 if (lock1 != lock2) spinlock_unlock;#endif return newObj;}
  • 旧指标解除注册操作 weak_unregister_no_lock

该办法主要职能是将旧指标在 weak_table 中接触 weak 指针的相应绑定。依据函数名,称之为解除注册操作。从源码中,能够精晓其职能正是从 weak_table 中接触 weak 指针的绑定。而其间的遍历查询,正是对准于 weak_entry 中的多张弱引用散列表。

  • 新对象增多注册操作 weak_register_no_lock

这一步与上一步相反,通过 weak_register_no_lock 函数把心的对象举办登记操作,实现与相应的弱援引表进行绑定操作。

  • 开首化弱援用对象流程一览

弱引用的伊始化,从上文的剖析中得以见到,主要的操作部分就在弱援引表的取键、查询散列、创设弱引用表等操作,能够计算出如下的流程图:

图片 5

SideTable 那一个结构体,是对weak_table_t表的重新卷入操作,防止对weak_table_t直接操作,SideTable使用特别有利。

class SideTable {private: static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];public: spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() : slock(SPINLOCK_INITIALIZER) { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { // never delete side_table in case other threads retain during exit assert; } static SideTable *tableForPointer(const void *p) {# if SIDE_TABLE_STRIPE == 1 return (SideTable *)table_buf;# else uintptr_t a = (uintptr_t)p; int index = ((a >> 4) ^ (a >> 9)) & (SIDE_TABLE_STRIPE - 1); return (SideTable *)&table_buf[index * SIDE_TABLE_SIZE];# endif } static void init() { // use placement new instead of static ctor to avoid dtor at exit for (int i = 0; i < SIDE_TABLE_STRIPE; i  ) { new (&table_buf[i * SIDE_TABLE_SIZE]) SideTable; } }};

一句话来讲依照上述对weak进行仓库储存的历程能够通过上面包车型客车流程图详细的汇报出来

图片 6

weak被保释为nil,须求对目标整个释放进程驾驭,如下是目的释放的完全流程:1、调用objc_release2、因为对象的援用计数为0,所以实行dealloc3、在dealloc中,调用了_objc_rootDealloc函数4、在_objc_rootDealloc中,调用了object_dispose函数5、调用objc_destructInstance6、最终调用objc_clear_deallocating。

对象盘算释放时,调用clearDeallocating函数。clearDeallocating函数首先依据指标地址获取具备weak指针地址的数组,然后遍历这一个数组把里面的数量设为nil,最终把那么些entry从weak表中除去,最终清理对象的记录。

在指标被放出的流程中,须求对objc_clear_deallocating方法实行浓重的解析

void objc_clear_deallocating { assert; assert; if (obj->isTaggedPointer return; obj->clearDeallocating();}//执行 clearDeallocating方法inline void objc_object::clearDeallocating(){ sidetable_clearDeallocating();}// 执行sidetable_clearDeallocating,找到weak表中的value值void objc_object::sidetable_clearDeallocating(){ SideTable *table = SideTable::tableForPointer; // clear any weak table items // clear extra retain count and deallocating bit // (fixme warn or abort if extra retain count == 0 ?) spinlock_lock(&table->slock); RefcountMap::iterator it = table->refcnts.find; if (it != table->refcnts.end { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table->weak_table, ; } table->refcnts.erase; } spinlock_unlock(&table->slock);}

对weak置nil的操作最后调用实施weak_clear_no_lock方法用于推行置nil的操作。施行格局如下:

/** * 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 %pn", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line) { referrers = entry->referrers; count = TABLE_SIZE; } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count;   i) { objc_object **referrer = referrers[i]; if  { 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, *referrer, referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry);}

objc_clear_deallocating该函数的动作如下:

1、从weak表中拿走扬弃对象的地址为键值的记录2、将包括在笔录中的全数附有 weak修饰符变量的地方,赋值为nil3、将weak表中该记录删除4、从援引计数表中删除舍弃对象的地址为键值的记录

骨子里Weak表是三个hash表,然后中间的key是指向目的的地方,Value是Weak指针的地址的数组。

总结

weak是Runtime维护了一个hash表,用于存款和储蓄指向有个别对象的有着weak指针。weak表其实是三个hash表,Key是所指对象的地址,Value是weak指针的地点(那一个地点的值是所指对象指针的地方)数组。

作者给大家推荐三个iOS本事交换群:923910776!群内提供数据结构与算法、底层晋级、swift、逆向、整合面试题等免费资料!

本文由星彩网app下载发布于计算机编程,转载请注明出处:关键字weak实现原理,iOS开发之weak底层实现原理

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。