iOS内存管理之sidetable

3,803 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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: image.png 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数组。