weak源码分析

int main(int argc, const char * argv[]) {
    NSObject *obj = [NSObject new];
    __weak id p = obj;
    return 0;
}

上面的代码,在编译成汇编的时候会添加两个函数:

callq   _objc_initWeak
callq   _objc_destroyWeak

可以在runtime的源码里找到对应的函数实现:

id
objc_initWeak(id *location, id newObj)
{
   ...
    return storeWeak
        (location, (objc_object*)newObj);
}

void
objc_destroyWeak(id *location)
{
    (void)storeWeak
        (location, nil);
}

先来分析下objc_initWeak里调用到的storeWeak。

SideTable

storeWeak函数里有这样一段代码:

 newTable = &SideTables()[newObj];

这个函数里会先通过hash获取对象obj对应的SideTable,先来了解下SideTable。

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}

SideTable里包含了弱引用表weak_table,SideTable存储在StripedMap的模板类里(可以把StripedMap看作一个哈希表)。StripedMap会以对象的地址作为key,经过哈希算法取出存储的SideTable。

//StripedMap是c++模板类,T是类型参数,这里把T看成SideTable类型
template
class StripedMap {
    enum { StripeCount = 64 };

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

   //SideTable存储在这个数组里,数组长度是64
    PaddedT array[StripeCount];

  //哈希算法,把这个函数的返回值用作数组的下标,p是对象的地址,% StripeCount是为了避免数组越界
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;//% StripeCount避免数组越界
    }

 public:
    //重写了[]操作符,可以通过StripedMap[对象地址]这样取出SideTable了。
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
};

从上面的代码里可以看出,StripedMap持有了一个长度是64的数组,数组里是SideTable
上面的哈希是有冲突的,indexForPointer()算出来的index值是0-63。多个对象可能会对应同一个SideTable。

下面看一下获取StripedMap的方法:

alignas(StripedMap) static uint8_t 
    SideTableBuf[sizeof(StripedMap)];
static void SideTableInit() {
    new (SideTableBuf) StripedMap();
}
static StripedMap& SideTables() {
    return *reinterpret_cast*>(SideTableBuf);
}

上面的代码不是特别明白,StripedMap是从SideTables()函数里获取的。SideTables()函数里大概是把静态数组SideTableBuf强转成了StripedMap类型。

storeWeak()

objc_initweak会调用到storeweak函数,storeweak函数里会先获取对象对应的sideTable,然后调用到weak_register_no_lock注册弱引用指针。

weak_table_t

之前已经获取到了obj对应的sidetable了,sidetable里就可以取到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_entries是一个数组,里面包含了weak_entry_t的结构体,一个对象对应一个weak_entry_t,在取出weak_entry_t,也用到哈希,对象obj作为哈希的key(把weak_table_t看作一个哈希表)。

//从weak_entries数组中通过hash取出weak_entry_t
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;
    //referent是对象的地址,以对象的地址为key作hash算出来的值作为数组的下标
    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];
}

static inline uintptr_t hash_pointer(objc_object *key) {
    return ptr_hash((uintptr_t)key);
}

static inline uint32_t ptr_hash(uint64_t key)
{
    key ^= key >> 4;
    key *= 0x8a970be7488fda55;
    key ^= __builtin_bswap64(key);
    return (uint32_t)key;
}

weak_entry_t

上面提到的方法通过obj哈希计算得到了对应的weak_entry_t对象,我们要存储的 p对象的的弱引用指针的地址是存储在weak_entry_t里的。
接下来看一下弱引用对象p的地址是怎么存出的。

struct weak_entry_t {
    DisguisedPtr 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里包含两个数组:

#define WEAK_INLINE_COUNT 4
weak_referrer_t *referrers;
 weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];

这两个数组里存储的是弱引用指针的地址。
如果对象的弱引用指针不超过四个,将弱引用指针的地址存储在inline_referrers里的,否则存储到referrers里。

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // Try to insert inline.
       //将弱引用指针的地址存储到inline_referrers里
        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.
        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);
    }
//这里又用到了hash,这里是用对象的弱引用指针的地址为key的,不是对象的地址,算出来的hash值作为数组的下标使用。
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
//解决哈希冲突
    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;
    }
//将弱引用对象的指针new_referrer放入数组referrers下标index的位置,weak_referrer_t &是c++引用的写法
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

上面的代码是先判断inline_referrers能否存储,如果已经存满了就使用referrers。使用referrers的时候有用到了hash,用弱引用指针的地址new_referrer作为key算出的值作为index,存到了数组里entry->referrers[index] = new_referrer。到这里就完成了弱引用指针的注册。

总结一下,注册weak的过程先用对象的地址为key,通过hash得到了对象对应的SideTable,然后再用对象的地址为key,通过hash从SideTable.weak_table里取出来weak_entry_t,然后会把弱引用指针的地址存储到weak_entry_t.inline_referrers里,如果weak_entry_t.inline_referrers已经存不下了,就用弱引用指针的地址做一个hash存储到了weak_entry_t.referrers中。

weak_unregister_no_lock

objc_destroyWeak会调用到storeweak,然后调用到weak_unregister_no_lock,然后remove_referrer():

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
    }

    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++;
        if (hash_displacement > entry->max_hash_displacement) {
            objc_weak_error();
            return;
        }
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}

首先也是先通过对象地址hash找到对象对应的weak_entry_t,然后会先从weak_entry_t. inline_referrers里遍历,如果找到了弱引用指针的地址,就移除弱引用指针的地址entry->inline_referrers[i] = nil;,否则就从weak_entry_t. referrers里通过hash去找,找到就移除entry->referrers[index] = nil; entry->num_refs--;,这个和上面讲到的注册弱应用是对应的。

int main(int argc, const char * argv[]) {
    NSObject *obj = [NSObject new];
    __weak id p = obj;
    return 0;
}

上面这种写法objc_destroyWeak是发生在obj对象调用dealloc之前的。也就是obj对象dealloc之前弱引用指针的地址已经移除了,并不会出现常说的对象释放的时候去把弱引用指针置nil。

__weak id pp = nil;
int main(int argc, const char * argv[]) {
    
    NSObject *obj = [NSObject new];
    __weak id pp = obj;
    return 0;
}

这种在汇编的时候会添加_objc_storeWeak,没有_objc_destroyWeak
当对象调用obj的dealloc方法时,会调用到clearDeallocating_slow,最后会调用到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);
    if (entry == nil) {
        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;
    }
    //过遍历数组,将弱应用指针*referrer 置nil
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
      
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

这里面也是新通过hash找到了weak_referrer_t,然后不管是weak_referrer_t .inline_referrers还是weak_referrer_t.referrers,最后都是通过遍历数组的方式,把所有的弱引用指针置成nil了。这就是在对象释放的时候,从哈希表里取出弱引用指针置nil。

总结:

int main(int argc, const char * argv[]) {
    NSObject *obj = [NSObject new];
    __weak id p = obj;
    return 0;
}

如上:
1.使用__weak修饰的对象p,首先会通过obj哈希得到对应的sidetable对象,获取到sidetable对象中的weak_table_t对象;
2.然后再通过obj哈希从weak_table_t中获取到对应的weak_entry_t对象;3.weak_entry_t对象中包含了一个长度是4的静态数组,弱引用对象p的地址会优先存储在静态数组里,超出长度限制后会用到weak_entry_t对象的动态数组,动态数组存储弱引用对象p的地址是通过弱引用对象p的地址哈希算出index作为数组下标存储的;
4.当对象obj释放时,通过上面的过程找到weak_entry_t的数组并通过遍历数组的方式将所有存储的弱引用对象置位nil。

你可能感兴趣的:(weak源码分析)