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<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (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<typename T>
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<uintptr_t>(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<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];
static void SideTableInit() {
    new (SideTableBuf) StripedMap<SideTable>();
}
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

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

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

#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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345