Runtime-object-weak阅读

objc-weak 阅读

内容来源于: objc4-750—> objct-weak.h/mm

此文件主要是实现管理对象的弱引用关系, 内部是通过一个SideTable来管理所有object的weak引用, 当该object引用技术为0被销毁时, 对象也会随之从引用表中删除. 通过弱引用机制, 可以避开对象的引用计数无法清0, 的循环引用(比较常见的例子是delegate之间的互相依赖)导致内存泄漏。

object-weak.h

这个文件主要定义了 weak_entry_tweak_table_t 以及weak对象的注册方法, 将对象的Id作为key存储, 并将引用他们的对象信息存储到 weak_table_t 中,

  • 全局的weak引用计数表, 用来存储弱引用对象的hash tab
struct weak_table_t {

   weak_entry_t *weak_entries; //weak引用实体的记录,内部通过 `inline_referrers` 记录所有持有的弱医用指针
   size_t    num_entries;
   uintptr_t mask;
   uintptr_t max_hash_displacement;

}; 

  • 将 对象的weak指针和对象本身以key-value的形式插入或从hash tab中删除
id weak_register_no_lock(weak_table_t *weak_table, id referent, id *referrer, bool crashIfDeallocating);

void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer); 

void weak_clear_no_lock(weak_table_t *weak_table, id referent);
  • weak指针记录, 通过 inline_referrers 记录了外部的weak指针的引用
struct weak_entry_t {
   

    //对objc_object指针重新包装,避免被系统内存检测工具误测
    DisguisedPtr<objc_object> referent; 
    
    //拷贝一个新的 `weak_entry_t`
    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }
    
    //当引用更新时候,重新替换,并将原来的关联的weak指针全部释放掉
    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指针到hash tab中

系统在注册时针对优化后的指针对象 TaggedPointer 的weak引用不做记录,因为该指针本身就保存对象的值

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{   
    //对象的指针
    objc_object *referent = (objc_object *)referent_id;
    //实例对象
    objc_object **referrer = (objc_object **)referrer_id;
    
    //主要对指针存储值的类型进行过滤
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}

weak引用的使用

主要分为以下几种: 创建weak指针, 获取weak指针, 拷贝weak指针, 销毁weak指针

  1. 在Objective-C中创建一个弱引用指针的时候( __weak typeof(object) weakObject = object; ), 会调用此方法将存储该weak指针, 如果weak指针引用为空, 则不会做处理

/** 
 * 初始化一个weak指针 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr; 
 * (The non-nil case) 
 * NSObject *o = ...; 
 * __weak id weakPtr = o; 
 * 
 * 此函数不是线程安全的, 因此后面在storeWeak方法内部进行了加锁
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
objc_initWeak(id *location, id newObj)
{

    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);

}

/** 
 * @param location weak指针的地址, 代表__weak申明的变量
 * @param obj weak指针引用的地址, 上面等号后面的具体的object实例
 */
OBJC_EXPORT id _Nullable
objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) 

    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

  1. 在任何地方通过 __weak 引用对象执行的表达式,根据传入的对象返回一个weak引用的指针,确保它的引用计数不受影响
OBJC_EXPORT id _Nullable
objc_loadWeak(id _Nullable * _Nonnull location)
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
  1. 此外还有 objc_moveWeak , objc_copyWeak , objc_destroyWeak , 通过 clange rewrite obc , 再从编译后的代码反查就知道对应的实际了.

weak引用的hashTable是怎么创建的

  1. 首先需要创建一个全局的 SideTables , 用于持有所有的 SideTable , SideTables 内部是由 SideTableBuf 定义的数组, 每个元素占8位置,
//要想创建
static StripedMap<SideTable>& SideTables() {

    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);

}

//系统不能使用C++静态初始化器初始化sidabess,因为
//在C++初始化器运行之前调用我们。我们也不希望使用一个全局的指针指向此结构的指针,因为存在额外的间接寻址。
alignas(StripedMap<SideTable>) static uint8_t 

    SideTableBuf[sizeof(StripedMap<SideTable>)];

static void SideTableInit() {

    new (SideTableBuf) StripedMap<SideTable>();

}

  1. 当在存储新的 weak指针和weak指针指向的object时,最终会以key-value的形式存储他们 newTable = &SideTables()[newObj]; , 对象的地址会作为key,每个对象会有很多个weak指针需要保存,所以需要为它再创建一个索引表 SideTable .
storeWeak(id *location, objc_object *newObj) {..

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    //此处用到了c的 `goto` 跳转语句并配合 `SideTable` 中自旋锁要一起使用,更新当前object的weak指针的索引表
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    //防止弱引用间死锁,确保引用的对象执行了被引用的对象isA非空,并且已经初始化
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            previouslyInitializedClass = cls;
            goto retry;
        }
    }

    //当对象销毁是,清空对应的hash tab的weak指针
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        //对象的弱引用指针最终被存储到SidleTab下的 `weak_table` 中,crashIfDeallocating指定当对象被销毁时是否需要抛出异常,这里主要是针对线程不安全的情况设计
        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.
    }
    //解锁返回新的weak指针
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
   
    return (id)newObj;
}
  • SideTable 主要控制了wak
struct SideTable {

    spinlock_t slock; //自旋锁,用于对SideTable的修改做加锁操作
    RefcountMap refcnts;
    weak_table_t weak_table;
    
    //通过调用 `memory set` 方法,初始化一个一个空格weakTable
    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);

}; 

小结

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

Objective-C为了解决对象之间引用计数带来的循环引用问题, 增加了 __weak 关键字, 弱引用机制, 通过它来实现对象之间的简介引用, 不增加引用计数也能访问到对象, 避免造成资源上的死锁. 通常在 闭包和属性传值(常见了有delegate)中容易出现问题, 通过 __weak 指针就能解除这种互相等待造成的思索问题.

Runtime为每个带weak引用的对象分配一个SideTable,保存在全局的SidleTables中,它们的关系如下(SideTables(以object地址为key, 并为其创建一个SideTable作为value) -> SideTable(加锁后更新weak_table) -> weak_table_t(存储weak指针)), 第一次初始化weak对象的的时候会创建一个SideTable, 并通过SideTab中的 weak_table_t 来存储当前对象所对应的所有weak指针, SideTable存放在一个全局的 SideTabs 中. 每个带有weak引用的object都有一个自己专属的SideTab, 它们以key-value的形式存储。key为对象自己本身, value则是在不同的地方定义的对该对象的引用指针, 在对象释放的时候会将该对象对应的SideTable清空.

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