美文网首页
Runtime学习-weak修饰符

Runtime学习-weak修饰符

作者: 心中有光啊 | 来源:发表于2022-09-06 15:34 被阅读0次

1、__weak修饰符的使用案例

在开发的过程中,可能回遇到循环引用的问题。所谓循环引用,当对象A持有了对象B,与此同时对象B同时也持有对象A时,此时对象A销毁需要对象B先销毁,而对象B销毁同样需要对象A先销毁,就导致相互等待销毁,此时对象A对象B的引用计数都不为0,所以对象A对象B此时都无法释放。 从而导致了内存泄漏。

解决循环引用最常用的方法就是使用__weak修饰符。

- (void)weakSelf{
    self.name = @"jack";
    //使用__weak修饰符后,若引用self,self的引用计数不会增加。
    __weak typeof(self) weakSelf = self;
    void(^block)(void) = ^{
        NSLog(@"weakSelf.name>>>>>%@",weakSelf.name);
    };
    block();
}

2、weak引用的流程图

weak断点.png
__weak打断点,查看相关的汇编代码,可以看到接下来要跳转的方法为objc_initWeak。在源码中查找该方法,该方法在NSObject.mm文件下。

方法:id objc_initWeak(id *location, id newObj)

  • id *location:弱应用修饰的对象的位置
  • id newObj:被若引用的对象
    weak的相关底层方法: weak相关底层方法.png
    从这些方法可以看出,弱引用是存储在sideTable中。sideTable具体内容这里不进行说明,只展示相关的数据结构,以便于后续的阐述。 sideTable的结构体.png

(1). 使用新的obj创建一个sideTableSideTable * newTable = &SideTables()[newObj];

*a、使用新值的指针,获得以 newObj 为索引所存储的值地址。通过hash找到newObj所在的SideTable使用weak引者的地址---disguise_ptr(oldObj),在SideTables中,查找到改地址对应的sideTable

(2). 使用原有的obj获取到对应的sideTableoldTable = &SideTables()[oldObj];

*a、使用weak引者的地址---disguise_ptr(oldObj),在SideTables中,查找到改地址对应的sideTable

(3). 使用weak_tablereferent来查找相关的entry:

static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)

*a、通过weak_table获取到weak_entriesweak_table->weak_entries
*b、遍历weak_entries,查找可以匹配referententry。[这里是通过对referent的哈希运算,并与weak_table->mask进行进行与操作,获取到index(与mask的与操作,可以有效的防止越界操作),在index位置没有匹配的referent,会自动向后匹配。以weak_table->max_hash_displacement为匹配的边界]

  /**********************************************************************/
  /*>>>>>计算出来entry在weak_table->weak_entries位置的index<<<<<*/
  /**********************************************************************/
  size_t begin = hash_pointer(referent) & weak_table->mask;
  size_t index = begin;
  size_t hash_displacement = 0;
  
  //weak_entries中取出index位置的referent,并不是要查询的被引用obj,继续循环
  while (weak_table->weak_entries[index].referent != referent) {
      //地址往后+1,并计算出来新的index
      //这个与操作可以有效的防止index的越界
      index = (index+1) & weak_table->mask;
      
      //index和begin相等,即证明所有的entries已经遍历完成。
      if (index == begin) bad_weak_table(weak_table->weak_entries);
      
      //遍历次数超过了max_hash_displacement,直接返回nil
      hash_displacement++;
      if (hash_displacement > weak_table->max_hash_displacement) {
          return nil;
      }
  }

*c、查找逻辑

weak_entry_for_referent.png

(4). 从entry的引用中,清除掉referrer

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)

*a、清除一个referrer,是遍历entryreferrersentry->referrers[index]),匹配对应的referrer
*b、最重要的工作还是要查找referrer对应的index。[这里是通过对referrer的哈希运算,并与entry->mask进行进行与操作,获取到index(与mask的与操作,可以有效的防止越界操作),在index位置没有匹配的referrer,也是会进行后向匹配。以entry->max_hash_displacement为匹配的边界]

  /**********************************************************************/
  /*>>>>>计算出来referre在entry->referrers若引用位置的index<<<<<*/
  /**********************************************************************/
  //hash函数计算出来位置,
  size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
  size_t index = begin;
  size_t hash_displacement = 0;
  //从计算出来的位置开始查询
  while (entry->referrers[index] != old_referrer) {
      index = (index+1) & entry->mask;
      if (index == begin) bad_weak_table(entry);
      hash_displacement++;
    
    //已经超出了max_hash_displacement,报错objc_weak_error,并返回
      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;
      }
  }

*c、找到了对应的referrer以后,把查询到的index位置的指向置为nil,即从referrers中删除了改referrer。enry的num_refs--操作

    /**********************************************************************/
    /*>>>>>找到了需要清除的referrer的位置,进行删除操作,并且把引用计数减1<<<<<*/
    /**********************************************************************/ 
 //找到了,从referre中删除这个弱引用对象
    entry->referrers[index] = nil;
  //引用计数减1
    entry->num_refs--;

*d、删除referrer操作

remove_referrer.png

(5). 删除entry的操作,即删除entryreferrers中为空的entry

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)

*a、当一个entry中,若引用对象的数量为0了,也就是referrers链表没有数据,这个entry就需要清除掉
*b、清除掉weak_table的一个entry后,需要处理weak_table的num_entries,进行减1操作

  /**********************************************************************************/
  /*>>>>>清除掉weak_table->entries中为空的entry,并且weak_table->num_entries减1操作<<<<<*/
  /**********************************************************************************/ 
  // remove entry
  if (entry->out_of_line()) free(entry->referrers);
  bzero(entry, sizeof(*entry));

  weak_table->num_entries--;
  //压weak_entryr中的entries的空间
/*
// Shrink if larger than 1024 buckets and at most 1/16 full.
  if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
      weak_resize(weak_table, old_size / 8);
      // leaves new table no more than 1/2 full
  }
*/
   weak_compact_maybe(weak_table);

*c、weak_table中清除entry

weak_entry_remove.png

(6). 创建entry并插入到一个weak_table的entries中:

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)

*a、当一个对象被弱引用,并且第一次被弱引用,首先要创建一个引用对象和被引用的对象entry

  /*********************/
  /*>>>>>新建entry<<<<<*/
   /*********************/
      //新建entry,
      weak_entry_t new_entry(referent, referrer);
      //判断entries是否需要扩容,需要的话,扩容原来尺寸的2倍
      weak_grow_maybe(weak_table);

*b、把新建的entry插入到weak_table->weak_entries[和删除entry时一样的操作,第一步是要根据referent通过hash函数计算出来index(需要和weak_table->mask进行与操作,防止越界),查询index处是否有数据。有数据的话,后向查询下一个位置,直到找到对应的index。或者超出weak_table的最大存放数量]

    /***************************************************************/
    /*>>>>>计算新建的entry在weak_table->weak_entries的的位置index<<<<<*/
     /**************************************************************/
  //获取到weak_entries
    weak_entry_t *weak_entries = weak_table->weak_entries;
  
        //函数函数计算存储位置(理解为数组下标)
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    //查找到位index的位置,如果有数据,就继续查找下一个位置
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }

*c、计算出来所在的位置后,进行插入操作,并且把weak_table->num_entries进行加1操作

   //找到空位置index,把new_entery插入到index位置
    weak_entries[index] = *new_entry;
    //弱引用计数加1
    weak_table->num_entries++;

*d、新建的entry插入到weak_table_entries

weak_entry_insert.png

(7). 已经被弱引用的对象referent,新加增弱引用操作。即entry->referres新增一个对象:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)

*a、在已经被弱引用的前提下,再次被另一个对象进行弱应用,就要在entry->referres新增一个成员
*b、在插入新的referre之前,会先判断referrers是不是需要进行扩容。如需要扩容,扩充为原来的两倍

    /********************/
    /*>>>>>扩容的操作<<<<<*/
    /********************/
  //referrers的带下尺寸,可以看出,如果需要扩容的话,是按照old_size的两倍进行开辟空间的
   size_t new_size = old_size ? old_size * 2 : 8;

    size_t num_refs = entry->num_refs;
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;
    
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));

*c、和其他的操作一样,还时必须要计算出来referrer所在的位置index

    /******************************************************/
    /*>>>>>计算新的referrer所在的index<<<<<*/
   /*>>>>>w_hash_pointer(new_referrer) & (entry->mask);<<<<<*/
    /******************************************************/
//计算出来在entry中的index位置
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    
    //查找index位置是不是有数据。有的话。就往后加一个位置,继续查询
    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;
    }

*d、将referrer插入到计算出来的index

  //取到index的地址,进行referrer的存储
  weak_referrer_t &ref = entry->referrers[index];
  ref = new_referrer;
  //弱引用指针个数加1
  entry->num_refs++;

*e、将referrer插入到计算出来的index

append_referrer.png

3、对象销毁时,weak引用的处理

当对象的引用计数为0的时候,或者调用了dealloc方法,runtime会调用_objc_rootDealloc

对象销毁的弱引用流程.png
  • 1、一个对象销毁的时候,会查询isa中的weakly_referenced参数,如果是1的话,需要清理弱引用。
  • 2、清理的时候,使用weak_table和referent查找到entry
  • 3、遍历entry中的referres,把里面的对象置为nil
  • 4、清理完成referrers后,删除掉entry

相关文章

网友评论

      本文标题:Runtime学习-weak修饰符

      本文链接:https://www.haomeiwen.com/subject/leffnrtx.html