zoukankan      html  css  js  c++  java
  • OC对象之旅 weak弱引用实现分析

    Runtime学习 -- weak应用源码学习

      Runtime源码分析,带你了解OC实现过程。其中参考了大量的大神的代码以及文献,里面也有个人的见解,欢迎拍砖,欢迎交流。

    两种常见使用场景

    /// weak属性
    @interface XX : XX
    @property(nonatomic,weak) Type* weakPtr;
    @end
    
    /// 代码块中使用
    {
        /// 使用__weak
        __weak Type* weakPtr = [[SomeObject alloc] init];
    }
    

    根据调试信息,发现两者的区别是:

    • 第一种进入到 id objc_storeWeak(id *location, id newObj)方法
    /** 
     * This function stores a new value into a __weak variable. It would
     * be used anywhere a __weak variable is the target of an assignment.
     * 
     * @param location The address of the weak pointer itself
     * @param newObj The new object this weak ptr should now point to
     * 
     * @return e newObj
     */
    id
    objc_storeWeak(id *location, id newObj)
    {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    
    • 第二种绕一个远路,先初始化 id objc_initWeak(id *location, id newObj)
    /** 
     * Initialize a fresh weak pointer to some object location. 
     * It would be used for code like: 
     *
     * (The nil case) 
     * __weak id weakPtr;
     * (The non-nil case) 
     * NSObject *o = ...;
     * __weak id weakPtr = o;
     * 
     * This function IS NOT thread-safe with respect to concurrent 
     * modifications to the weak variable. (Concurrent weak clear is safe.)
     *
     * @param location Address of __weak ptr. 
     * @param newObj Object ptr. 
     */
    id objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    
    • 两者最终进入到如下方法
    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
    static id
    storeWeak(id *location, objc_object *newObj)
    {
        ///略去,下面会进行分析 
        ...
        return (id)newObj;
    }
    

    所以重点就在 storeWeak这个方法中,let's do it

    分析源码

    storeWeak源码的如下:

    template <HaveOld haveOld, HaveNew haveNew,
              CrashIfDeallocating crashIfDeallocating>
    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];
        } 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;
        }
        // Prevent a deadlock between the weak reference machinery
        // and the +initialize machinery by ensuring that no 
        // weakly-referenced object has an un-+initialized isa.
        /// 注释大意是通过下面操作,保证所有的弱引用对象的isa都被初始化,这样可以防止死锁,PS,这里我不是太明白,求指教
        if (haveNew  &&  newObj) {
            /// 下面的操作是初始化isa
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
    
        // 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;
    }
    
    • template <HaveOld haveOld, HaveNew haveNew,CrashIfDeallocating crashIfDeallocating>是C++的一种泛型实现,相当于这里申明了变量或者类型,可以在代码块中使用,用于处理不同的未知类型&枚举。
    • haveOld 弱引用是否已经有所指向
    • haveNew 是否有新的指向
    • CrashIfDeallocating 执行方法时发生Deallocate是否Crash

    PS:初始化ISA那部分为何能阻止死锁,我没有看懂
    该函数流程如下:


    流程图

    重点来了:

    /// SideTables
    oldTable = &SideTables()[oldObj];
    newTable = &SideTables()[newObj];
    /// taggedPointer是什么鬼
    isTaggedPointer
    /// 注册弱引用
    weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating);
    /// 消除弱引用
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    

    SideTable

    SideTable是一个结构体,定义如下

    struct SideTable {
        spinlock_t slock;
        RefcountMap refcnts;
        weak_table_t weak_table;
    
        SideTable() {
            memset(&weak_table, 0, sizeof(weak_table));
        }
    
        ~SideTable() {
            _objc_fatal("Do not delete SideTable.");
        }
    
        ///锁
        ....
    };
    
    • spinlock_t solck 锁
    • RefcountMap refcnts 强引用使用,略过
    • weak_table_t weak_table 弱引用表
      SideTable是存放引用关系的,对象通过Hash值操作,在SideTableBuf 中寻找与之对应的SideTableSideTableBuf初始化过程如下:
    alignas(StripedMap<SideTable>) static uint8_t 
        SideTableBuf[sizeof(StripedMap<SideTable>)];
        /// 会在Objc_init中调用该方法
    static void SideTableInit() {
        /// 这句话貌似没什么卵用,求指教
        new (SideTableBuf) StripedMap<SideTable>();
    }
    /// 寻找SideTable
    static StripedMap<SideTable>& SideTables() {
        return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }
    

    StripedMap是一个泛型类,并重写了[]运算符,通过对象的地址,运算出Hash值,通过该hash值找到对象的SideTable

    template<typename T>
    class StripedMap {
        enum { CacheLineSize = 64 };
    #if TARGET_OS_EMBEDDED
        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);
            /// 位运算可以控制返回值在0-63之间
            return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
        }
    
     public:
        T& operator[] (const void *p) {
            return array[indexForPointer(p)].value; 
        }
        /// 下面略去
        ...
    }
    

    taggedPointer

    简单的说,这是一种优化手段,即将对象的值,存入对象的地址中,这些工程师简直丧心病狂,就为了省一点内存嘛!

    进入正题,看看怎么实现弱引用的

    先看看注册的过程吧

    /** 
     * 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)
    {
        /// 转化为object
        objc_object *referent = (objc_object *)referent_id;
        objc_object **referrer = (objc_object **)referrer_id;
        /// 如果是taggedPointer,就没有引用的过程了
        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;
    }
    

    先从这行数的参数说起,参数有4个

    • weak_table_t *weak_table hash表
    • id referent_id, 弱引用对象
    • id *referrer_id, 弱引用指针
    • bool crashIfDeallocating 如果正在Deallocate是否crash

    后三个参数不用解释,主要解释第一个参数,weak_table_t,定义如下

    /**
     * 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_table_t就是寄存在SideTable

    • weak_entry_t *weak_entries; ///数组,用于存储引用对象集合
    • size_t num_entries; /// 存储数目
    • uintptr_t mask; /// 当前分配容量
    • uintptr_t max_hash_displacement; /// 已使用容量

    定义中我们重点关注weak_entry_t

    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& operator=(const weak_entry_t& other) {
            memcpy(this, &other, sizeof(other));
            return *this;
        }
    
        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_entry_t是最终存放对象和引用指针的地方,referent是被引用的对象,联合体union释义如下

    • weak_referrer_t *referrers; 存放引用指针
    • uintptr_t out_of_line_ness : 2 标识当前存储是否在初始WEAK_INLINE_COUNT个数之内
    • uintptr_t num_refs : PTR_MINUS_2 引用的个数
    • uintptr_t mask; 实际分配容量
    • uintptr_t max_hash_displacement; 实际使用容量,包括已经被释放的,每次调整容量时会更新重置
    • weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 当引用个数小于WEAK_INLINE_COUNT时,使用该数组存放。

    注册引用过程中,重点关注下面代码:

    {
    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_table_t 的容量大小
            weak_grow_maybe(weak_table);
            /// 插入一个
            weak_entry_insert(weak_table, &new_entry);
        }
    }
    

    新建

    通过weak_entry_t的源码,可以看到新建一个weak_entry_t的过程是

    • 将被引用对象赋予referent
    • 将引用指针放入到inline_referrers,因为此时数目还很少

    调整weak_table_t的容量大小

    static void weak_resize(weak_table_t *weak_table, size_t new_size)
    {
        size_t old_size = TABLE_SIZE(weak_table);
    
        weak_entry_t *old_entries = weak_table->weak_entries;
        weak_entry_t *new_entries = (weak_entry_t *)
            calloc(new_size, sizeof(weak_entry_t));
    
        weak_table->mask = new_size - 1;
        weak_table->weak_entries = new_entries;
        /// 重置
        weak_table->max_hash_displacement = 0;
        weak_table->num_entries = 0;  // restored by weak_entry_insert below
        
        if (old_entries) {
            weak_entry_t *entry;
            weak_entry_t *end = old_entries + old_size;
            for (entry = old_entries; entry < end; entry++) {
                if (entry->referent) {
                    weak_entry_insert(weak_table, entry);
                }
            }
            free(old_entries);
        }
    }
    
    // Grow the given zone's table of weak references if it is full.
    static void weak_grow_maybe(weak_table_t *weak_table)
    {
        size_t old_size = TABLE_SIZE(weak_table);
    
        // Grow if at least 3/4 full.
        if (weak_table->num_entries >= old_size * 3 / 4) {
            weak_resize(weak_table, old_size ? old_size*2 : 64);
        }
    }
    

    当实际的数目大于old_size(old_size就是mask的大小+1),就去调整大小,同时重置max_hash_displacement为0,通过calloc函数,动态分配mask个的内存,然后通过循环,将原有的weak_entry_t插入到新的容器中,在插入的过程中,更新max_hash_displacement.

    weak_table_t插入weak_entry_t

    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;
        /// 引用计数+1
        weak_table->num_entries++;
        /// 扩容前最大占位
        if (hash_displacement > weak_table->max_hash_displacement) {
            weak_table->max_hash_displacement = hash_displacement;
        }
    }
    

    过程比较简单,也是利用hash处理,方便后面查找。

    weak_table_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;
    
        size_t begin = hash_pointer(referent) & weak_table->mask; /// 获取hash值
        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_entry_t中加入引用

    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.
            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;
        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_table_t中插入weak_entry_t如出一辙,要注意的是需要判断引用的个数,当引用个数大于WEAK_INLINE_COUNT时,需要将原有的引用指针也移到referrers中,同时更新相关计数器。
    上面过程的流程如下:

    查找流程

    消除弱引用

    消除弱引用过程同注册大致相同,只是部分地方是相反操作,不做赘述了

  • 相关阅读:
    4.8 C++ typeid操作符
    4.7 C++ dynamic_cast操作符
    tomcat中class和jar的加载顺序(转)
    java Files类和Paths类的用法 (转)
    搭建DUBBO项目解决DUBBO.XML标签报错的问题(转载)
    Maven异常:Could not find artifact
    在docker宿主机上查找指定容器内运行的所有进程的PID
    jmap、jstack、jps无法连接jvm解决办法
    linux中如何查看某一进程的启动时间
    Eureka与ZooKeeper 的比较(转)
  • 原文地址:https://www.cnblogs.com/wws19125/p/6978563.html
Copyright © 2011-2022 走看看