weak的实现原理

weak的实现原理总结记录

根据代码:

NSObject *obj = [[NSObject alloc] init];
__weak NSObject *weakObj = obj;

来分析weak的实现过程。

  1. objc_initWeak

运行后,首先会调用objc_initWeak,上述代码会变成objc_initWeak(&weakObj, obj)obj_initWeak方法的具体实现如下:

id
objc_initWeak(id *location, id newObj)
{
 if (!newObj) {
 *location = nil;
 return nil;
 }
​
 return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
 (location, (objc_object*)newObj);
}

1、进行判空,若被引用对象为空,则不执行;

2、调用storeWeak方法,上述代码应为storeWeak(&weakObj, obj)

  1. storeWeak

storeWeak方法的具体实现如下:

template <HaveOld haveOld, HaveNew haveNew,
 CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
 assert(haveOld  ||  haveNew);
 if (!haveNew) assert(newObj == nil);
​
 Class previouslyInitializedClass = nil;
 id oldObj;
 SideTable *oldTable;
 SideTable *newTable;
​
 // Acquire locks for old and new values.
 // Order by lock address to prevent lock ordering problems. 
 // Retry if the old value changes underneath us.
 retry:
 if (haveOld) {
 oldObj = *location;
 oldTable = &SideTables()[oldObj];
 } else {
 oldTable = nil;
 }
 if (haveNew) {
 newTable = &SideTables()[newObj];
 } else {
 newTable = nil;
 }
​
 SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
​
 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.
 if (haveNew  &&  newObj) {
 Class cls = newObj->getIsa();
 if (cls != previouslyInitializedClass  &&
 !((objc_class *)cls)->isInitialized()) 
 {
 SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
 _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
​
 // If this class is finished with +initialize then we're good.
 // If this class is still running +initialize on this thread 
 // (i.e. +initialize called storeWeak on an instance of itself)
 // then we may proceed but it will appear initializing and 
 // not yet initialized to the check above.
 // Instead set previouslyInitializedClass to recognize it on retry.
 previouslyInitializedClass = cls;
​
 goto retry;
 }
 }
​
 // Clean up old value, if any.
 if (haveOld) {
 weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
 }
​
 // Assign new value, if any.
 if (haveNew) {
 newObj = (objc_object *)
 weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
 crashIfDeallocating);
 // 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 = (id)newObj;
 }
 else {
 // No new value. The storage is not changed.
 }

 SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
​
 return (id)newObj;
}

这里涉及到C++的模板概念,由于对模板不太了解,所以就仅看精简后的haveNew为TRUE的分支,精简后的主要代码及注释如下:

static id 
storeWeak(id *location, objc_object *newObj)
{
 Class previouslyInitializedClass = nil;
 id oldObj;
 SideTable *oldTable;
 SideTable *newTable;
​
 retry:
 if (haveOld) {
 oldObj = *location;
 oldTable = &SideTables()[oldObj];
 } else {
 oldTable = nil;//没有旧的table
 }
 if (haveNew) {
 newTable = &SideTables()[newObj];//获取obj对应的SideTable实例
 } else {
 newTable = nil;
 }
​
 if (haveNew) {
 newObj = (objc_object *)
 weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
 crashIfDeallocating);//注册并存储弱引用对象
​
 //设置是否包含弱引用的标识位
 if (newObj  &&  !newObj->isTaggedPointer()) {
 newObj->setWeaklyReferenced_nolock();
 }
​
 //设置地址指向
 *location = (id)newObj;
 }
 else {
 // No new value. The storage is not changed.
 }
​
 return (id)newObj;
}

其中的weak_register_no_lock方法实现了弱引用对象的注册和存储。

  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;//obj
 objc_object **referrer = (objc_object **)referrer_id;//&weakObj
​
 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;
}

同样取精简代码进行分析,精简后的代码和注释如下:

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;//这里指的是obj
 objc_object **referrer = (objc_object **)referrer_id;//这里指的是weakObj的指针
​
 weak_entry_t *entry;
 //获取obj对应的弱引用表,若能获得,则把weakObj添加进弱引用表中;否则新建一个弱引用表
 if ((entry = weak_entry_for_referent(weak_table, referent))) {
 append_referrer(entry, referrer);//具体代码不贴了,主要逻辑就是在获得的弱引用表的最后加入weakObj,并把count加一
 } 
 else {
 weak_entry_t new_entry(referent, referrer);
 weak_grow_maybe(weak_table);
 weak_entry_insert(weak_table, &new_entry);
 }
​
 return referent_id;
}

weak_entry_for_referent方法实现了弱引用表的hash查找,具体实现如下:

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
 weak_entry_t *weak_entries = weak_table->weak_entries;//获得全局的弱引用表
​
 if (!weak_entries) return nil;
​
 size_t begin = hash_pointer(referent) & weak_table->mask;//hash算法获得索引
 size_t index = begin;
 size_t hash_displacement = 0;
 //hash碰撞处理,逐步获得正确的索引
 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;
 }
 }

 //通过索引返回Obj对应的弱引用表
 return &weak_table->weak_entries[index];
}
  1. 释放过程

方法调用过程是:

clearDeallocating -> weak_clear_no_lock

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 %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) {
 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);
}

主要流程就是遍历弱引用数组,把每个弱引用对象置为nil,然后把当前的弱引用表从全局表中删除。

  1. 涉及的各种结构体以及相互关系

上述代码中涉及的结构体及相互关系可见下图:

image-20190215160856500.png

其中需要说明的点是:

(1)SideTable是引用计数和弱引用依赖表,runtime用一个静态数组作为Buffer保存了所有的SideTable实例,通过提供的工厂方法,可以根据对象获得对应的SideTable实例;

(2)每一个SideTable所持有的weak_table都是全局的弱引用表,根据对象地址,通过hash算法,可以获得一个包含原对象(obj)和其对应的弱引用对象或者弱引用对象数组,就是一个weak_entry_t。

  1. 总结

概括上述内容,weak的实现原理是:

简单来说,是由runtime维护了一个弱引用表,通过hash算法,可以获得相应对象的弱引用数组,并将新的弱引用对象指针添加到弱引用数组中。

  1. 参考资料

http://yulingtianxia.com/blog/2015/12/06/The-Principle-of-Refenrence-Counting/

https://www.jianshu.com/p/13c4fb1cedea

http://solacode.github.io/2015/10/21/Runtime%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0weak%E5%B1%9E%E6%80%A7%EF%BC%9F/

http://www.cocoachina.com/ios/20170328/18962.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容