Runtime--SideTable

2,250 阅读7分钟

散列表SideTables是内存管理的方案之一,是重要表之一。

包含了引用计数表弱引用表

在源码map_images_nolock中,我们之前都是直接定位到最后的read_images方法,这个方法用来加载类。在加载类之上,找到下面代码。

...
if (firstTime) {
    //这个方法会初始化方法表,并注册用到的内置方法,如load,initialize,retain,autorelease等。
    sel_init(selrefCount); 
    arr_init(); 
}
.....

void arr_init(void) 
{
    AutoreleasePoolPage::init();
    SideTableInit(); // 初始化SideTable
}

static void SideTableInit() {
    new (SideTableBuf) StripedMap<SideTable>();
}

在使用时,调用的是SideTables。如下:

table = &SideTables()[obj];

//获取全局静态SideTables
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

看下StripedMap的结构。

enum { CacheLineSize = 64 };
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }
.....
};

通过上面代码看出,SideTables是一个全局hash数组,里面每一个元素是一个SideTableSideTables的数组长度,来自StripedMap中的StripeCount

查看SideTable的结构。

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

其中SideTable中又包含了引用计数表弱引用计数表。所以,会存在多个对象共用一个SideTable

  • 如何找到对象所在的SideTable

    通过indexForPointer方法,作hash算法,找到所在的SideTable

  • 为什么不直接用一张SideTable,而是用SideTables去管理多个SideTable

    SideTable里有一个自旋锁,如果把所有的类都放在同一个SideTable,有任何一个类有改动都会对整个table做操作,并且在操作一个类的同时,操作别的类会被锁住等待,这样会导致操作效率和查询效率都很低。而有多个SideTable的话,操作的都是单个Table,并不会影响其他的table,这就是分离锁。

全局的引用计数之所以不存在同一张表中,是为了避免资源竞争,解决效率的问题。引用计数表 中引入了 分离锁的概念,将一张表分拆成多个部分,对他们分别加锁,可以实现并发操作,提升执行效率

  • 如何找到对象的引用计数?

    先找到所在的SideTable,然后搜索SideTable中的refcntsrefcnts也是一张hash表,key为对象的地址,value是引用计数值。

根据上文我们知道,一个SideTable中存储了多个对象的信息,信息分为引用计数和弱引用关系。

接下来,我们看下⚠️⚠️重要的weak_table。

首先明确,一个weak_table中存储了多个对象的弱引用信息。看下结构。

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

weak_entries是一个动态数组,对象通过hash算法,找到weak_entry_t。即指向对象的弱引用信息。看下结构。

#define WEAK_INLINE_COUNT 4
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];
        };
    };
    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_INLINE_COUNT时,会使用定长数组inline_referrers。而当大于WEAK_INLINE_COUNT时,则会转换到动态数组模式weak_referrer_t*referrers。这个地方在调用的时候会进行判断。

初始化一个weak的对象

 Person *p = [[Person alloc] init];
 __weak Person *p1 = p;

p = 0x100f3c6f0

在第二行代码时,进入到runtime层。

id
objc_initWeak(id *location, id newObj)
{
    //【这里location用id*,newObj用id,说明location=指针的地址,后面的函数中,使用到的location都是指针的地址】
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

此时

(id *) location = 0x7ffeefbff4e0,即p1的地址
newObj = (Person *)	0x100f3c6f0

进入到storeWeak

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]; //【根据weak指针找到其指向的老的对象】
    } else {
        oldTable = nil; //【走到这里,此时haveOld=false】
    }
    if (haveNew) {
        newTable = &SideTables()[newObj]; //【通过hash找到newObj所在的SideTable】
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
    .....//省略
    
    // 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;
}

查看weak_register_no_lock

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    //【(Person *) referent_id = 0x0000000100f3c6f0】
    objc_object *referent = (objc_object *)referent_id;
    
    //【(id *) referrer_id = 0x00007ffeefbff4e0】
    objc_object **referrer = (objc_object **)referrer_id;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    .......

    // now remember it and where it is being stored
    weak_entry_t *entry;
    //【先查看是否已经存在以对象为key的弱引用关系表】
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //【存在,直接追加】
        append_referrer(entry, referrer); 
    } 
    else {
        //【不存在当前对象地址为key的弱引用表】
        //【通过构造方法,初始化并插入referrer在第一个】
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);//【是否超过3/4,如果是,扩容,*2,初始值为64】
        //【将新的new_entry加入到weak_table中】
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

至此,初始化操作结束。我们再来看下=nil的场景。

weak的释放

⚠️⚠️重点来了,常问的面试题当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

Person *p = [[Person alloc] init];
__weak Person *p1 = p; //【初始化操作】
p = nil; //【对象释放】

当释放对象时,其基本流程如下图:

直接进入到weak_clear_no_lock

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    //类型转换
    objc_object *referent = (objc_object *)referent_id;
    //找到weak_entry_t
    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); //移除entry
}

这个函数首先是找出对象对应的weak_entry_t链表,然后将弱引用置为nil。最后清理对象的记录。如图所示。

注意的是,referrers中存放的是指针的地址。

一个weak引用的处理涉及各种查表、添加与删除操作,还是有一定消耗的。所以如果大量使用__weak变量的话,会对性能造成一定的影响。那么,我们应该在什么时候去使用weak呢?《Objective-C高级编程》给我们的建议是只在避免循环引用的时候使用__weak修饰符。

weak_entry_for_referent

/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    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;
        }
    }
    
    return &weak_table->weak_entries[index];
}

遍历weak_table->weak_entries,通过hash算法,计算index,取出weak_entry_t,判断referent是否相等,如果相等,说明存在以当前对象地址为key的弱引用表,返回weak_entry_t。

append_referrer

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    //【没有使用动态数组时】
    if (! entry->out_of_line()) { 
        // Try to insert inline.
        //【记录,这里是**,所以是 指针的地址】
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return; //【如果定长满足,直接返回】
            }
        }

        // Couldn't insert inline. Allocate out of line.
        //【不能插入到inline_referrers,空间已满,分配动态数组,转为referrers】
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }
    //【下面是动态数组添加数据,此时 一定是动态数组,如果定长满足,走不到这里】
    assert(entry->out_of_line());

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        //【扩容操作+追加操作】
        return grow_refs_and_insert(entry, new_referrer);
    }
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    //【hash找到空位置】
    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;
    }
    //【找到空位置,存放】
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

weak_entry_insert

/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 */
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    assert(weak_entries != nil);
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }
    weak_entries[index] = *new_entry;
    weak_table->num_entries++;

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}