• # objc-weak 阅读


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

    此类主要是实现管理对象的弱引用关系, 内部是通过一个hash tab来管理所有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中删除
    
     ```C++
     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指针的引用
    
    ```C++
    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清空.

    clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 -isysroot /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m

  • 相关阅读:
    云原生体系下 Serverless 弹性探索与实践
    PaddlePaddle:在 Serverless 架构上十几行代码实现 OCR 能力
    manjaro kde系统格式化U盘
    从B站看到的资源网站(确实是很不错,推荐!)
    ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysql 或 manjaro 安装 mariadb
    manjaro kde 我安装后使用到的命令
    思考了许久,博客在未来将会进行的改变(2021-10-03)
    linux 下配置 python源
    ModuleNotFoundError: No module named 'distutils.util'
    mysqlclient 安装失败
  • 原文地址:https://www.cnblogs.com/wwoo/p/objcweak-yue-du.html
Copyright © 2020-2023  润新知