开启生长之旅!这是我参加「日新计划 12 月更文应战」的第4天,点击检查活动概况

一、sidetable

直接检查objc源码找到sidetable_addExtraRC_nolock

bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
  ASSERT(isa.nonpointer);
  SideTable& table = SideTables()[this];//获取目标的SideTable
  size_t& refcntStorage = table.refcnts[this];
  size_t oldRefcnt = refcntStorage;
  // isa-side bits should not be set here
  ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
  ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
  if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
  uintptr_t carry;
  size_t newRefcnt =
    addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
  if (carry) {
    refcntStorage =
      SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
    return true;
  }
  else {
    refcntStorage = newRefcnt;
    return false;
  }
}

咱们来看下SideTable的数据结构:

struct SideTable {
  spinlock_t slock;//确保线程安全  os_unfair_lock mLock; 是互斥锁
  RefcountMap refcnts;//存储目标的引证计数
  weak_table_t weak_table;//弱引证表,加锁 解锁 低效
  SideTable() {
    memset(&weak_table, 0, sizeof(weak_table));
  }
  ~SideTable() {
    _objc_fatal("Do not delete SideTable.");
  }
  void lock() { slock.lock(); }
  void unlock() { slock.unlock(); }
  void forceReset() { slock.forceReset(); }
  // Address-ordered lock discipline for a pair of side tables.
  template<HaveOld, HaveNew>
  static void lockTwo(SideTable *lock1, SideTable *lock2);
  template<HaveOld, HaveNew>
  static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

在进入SideTables()的源码:

static StripedMap<SideTable>& SideTables() { //创立获取StripedMap list  真机8个
  return SideTablesMap.get();
}

在看下StripedMap:

iOS内存管理之sidetable
StripedMap是用做:缓存带有spinlock_t锁的才能的类或者是结构体。这个map的个数是固定的,模拟器64个,真机是8个。

我们可以思考一下,在整个项目中只初始化了一个SideTable和所有目标的weak_table_t表。可是这样的效率会很低,由于有spinlock_t加锁,解锁而形成低效的问题。可是假如每个目标都创立SideTable和weak_table_t表,效率是高了可是内存占用过高

apple是怎么解决呢,用了什么方案呢?

来咱们直接看StripedMap部分源码:

    PaddedT array[StripeCount];
    //中心算法,均匀分配到真机8张表中。
  static unsigned int indexForPointer(const void *p) {
    uintptr_t addr = reinterpret_cast<uintptr_t>(p);
    return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
  }

方法indexForPointer经过目标的地址,来确定是在array中的第几个元素,进而拿到哈希值。
而要害的地方在于,这个类对[]运算符进行了重载,前面取出对应的SideTable用的方法是SideTables()[this],它的翻译过来的实际操作是:SideTables().array[indexForPointer(this)].value,便是先经过indexForPointer计算出位置,在经过array[index]取出值,便是对应的SideTable

弱引证表 weak_table_t weak_table

直接进入weak_table_t源码检查:

struct weak_table_t {
  weak_entry_t *weak_entries;
  size_t  num_entries;
  uintptr_t mask;
  uintptr_t max_hash_displacement;
};

在进入weak_entry_t源码检查:

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 {
      // out_of_line_ness field is low bits of inline_referrers[1]
      weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
    };
  };
  bool out_of_line() {
    return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
  }
  weak_entry_t& operator=(const weak_entry_t& other) {
    memcpy(this, &other, sizeof(other));
    return *this;
  }
  weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
    : referent(newReferent)
  {
    inline_referrers[0] = newReferrer;
    for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
      inline_referrers[i] = nil;
    }
  }
};

weak_entry_t存储着某个目标的弱引证信息,又是一个结构体。跟weak_table_t相似,里边存储一个hash表weak_referrer_t,弱引证该”目标的指针”的指针。经过weak_referrer_t的操作,可以使该目标的弱引证指针在目标开释后,置为nil。

DisguisedPtr<objc_object> referent :弱引证目标指针摘要。其实可以理解为弱引证目标的指针,只不过这里运用了摘要的方式存储。(所谓摘要,其实是把地址取负)。

weak_referrer_t:这是一个共用体,分动态数组和固定长度数组两种情况,

out_of_line:bool类型区别是weak_referrer_t中数组类型

weak_entry_t& operator=(const weak_entry_t& other) :赋值

weak_entry_t(objc_object *newReferent, objc_object **newReferrer) 构造

在检查weak_referrer_t *referrers; 的源码完成: typedef DisguisedPtr<objc_object *> weak_referrer_t;

weak_referrer_t以指针摘要的方式,存储弱引证指针的指针。weak_referrer_t数组这里设置了两种模式,究竟动态数组涉及到更多的操作,在小数据量的情况下,运用定长数组效率更高。

总结

iOS的一个项目:会全局保护一个SideTables,SideTables里边包含多个sidetable(真机情况下是8个)。

sidetable中有:spinlock_t(确保线程安全),RefcountMap(存储目标的引证计数),weak_table_t(弱引证表,加锁 解锁 低效) 三个中心属性。

weak_table_t:中保存着的一个sidetable中所有weak_entries表,从weak_entries中经过目标查找着某个目标对应的弱引证信息weak_entry_t,weak_entry_t中保存着弱引证该目标的 指针地址的hash数组。