八、弱应用(Weak)的实现

2018-06-27  本文已影响0人  LNG61

一、弱引用Weak初始化

我们直接在main函数加上以下代码:

NSObject *object1 = [NSObject new];
NSObject *object2 = [NSObject new];
__weak NSObject *weakRef = object1;
weakRef = object2;
object2= nil;
图一
如上图所示,在__weak NSObject *weakRef = object1;语句之后,step into执行的下一个函数为objc_initWeak,通过这个命名我们可以猜测是用来初始化弱应用的。
id objc_initWeak(id *location, id newObj){
  if(!newObj){
    // 赋值为nil
    *location = nil;
    return nil;
  }

  return storeWeak<false, true, true>(location, (objc_object *)newObj);
}

objc_initWeak该方法主要是storeWeak的一个入口,接下来看下storeWeak是怎样实现的。

template <bool haveOld, bool haveNew, bool crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj){
  id oldObj;
  SideTable *oldTable, *newTable;
 retry:
  if(haveOld){
    // 如果有旧对象,则获取
    oldObj = *location;
    oldTable = &SideTables()[oldObj];
  }else{
    oldTable = nil;
  }

  if(haveNew){
    newTable = &SideTables()[newObj];
  }else{
    newTable = nil;
  }

  // 其它逻辑处理

  if(haveOld){
      // 清理旧对象
      weak_unregister_no_lock(&oldTable->weakTable, oldObj, location);
  }

  if(haveNew){
      // 赋新值
      newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);
      newObj->setWeaklyReferenced_nolock();
      *location = (id)newObj;
  }

  return (id)newObj;
}

storeWeak的主要逻辑是将获取到oldObj和newObj对应的弱引用表,然后在旧表中移除旧值,在新表中添加新值。这里涉及到SideTables(),其主要作用是返回一个全局的哈希表StripedMap<SideTable>,以对象的哈希值作为索引。
SideTable的结构:

struct SideTable{
  spinlock_t slock; // 锁
  RefcountMap refcnts; // 引用计数
  weak_table_t weak_table; // 弱引用表
}

struct weak_table_t{
  weak_entry_t *weak_entries; //弱引用登记
  size_t num_entries;
  uintptr_t mask;
  uintptr_t max_hash_displacement;
}

struct weak_entry_t{
  DisguisedPtr<objc_object> referent; // 被引用的对象
  union{
    struct{
      weak_referrer_t *referrers; // 所有弱引用该对象的指针地址
      uintptr_t out_of_line_ness : 2;
      uintptr_t  num_refs : PTR_MINUS_2;
      uintptr_t  mask;
      uintptr_t  max_hash_displacement;
    };
    struct{
      weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //优化后的
    }
  }
}

每个被弱引用的对象对应一个weak_entry_t结构,然后在referrersinline_referrers中保存这所有弱引用该对象的对象地址。

二、弱引用注册和移除

方法storeWeak通过weak_unregister_no_lock来移除一个弱引用,通过weak_register_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 = weak_entry_for_referent(weak_table, referent);
  if(entry){
     remove_referrer(entry, referrer);
     if(empty){
        // 如果referrer没有被弱引用了,则移除弱引用表
        weak_entry_remove(weak_table, entry);
     }
  }
}

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;

  // 处理是否dealloc的逻辑

  // 插入table中
  weak_entry *entry = weak_entry_for_referent(weak_table, referent);
  if(entry){
    // entry 已经存在
    append_referrer(entry, referrer);
  }else{
     // 生成新entry
    weak_entry_t new_entry(referent, referrer);
    weak_grow_maybe(weak_table);
    weak_entry_insert(weak_table, &new_entry);
  }

  return referent_id;
}

三、弱引用置为nil

我们知道,当弱引用的对象被释放时,所有指向该对象的弱引用都会被置为nil,接下来我们来看下这个是如何实现的。在最开始的例子中,后面有这样一段代码:

weakRef = object2;
object2 = nil;

同样我们通过断点的方式看下这过程是如何调用的,如下图:

图二
经过一系列的调用之后,最终是调用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);
  for(size_t i = 0; i < WEAK_LINE_COUNT; ++i){
    object **referrer = referrers[i];
    if(*referrer == referent){
      // 如果该对象是指向referent_id,则置为nil
      *referrer = nil; 
    }
  }
  weak_entry_remove(weak_table, entry);
}

在上面的weak_clear_no_lock中,referent就是object2,然后遍历object2所有的弱引用,置为nil,这样就实现了对象释放后,所以指向该对象的弱引用自动变为nil的过程。

小结

  1. 弱引用是一个全局哈希表,每个对象对应一个weak_entry_tweak_entry_t下面保存了所有指向该对象的弱引用指针。
  2. 对象释放后,会调用weak_clear_no_lock方法将所有指向该对象的弱引用指针赋值为nil。
上一篇 下一篇

猜你喜欢

热点阅读