Objective-C中weak实现原理

Objective-C的对象采用引用计数来管理内存,如果对象被强持有,这个对象的引用计数会增加,如果对象被弱持有,这个对象的引用计数不会增加。弱持有也就是weak如何实现的呢?首先看下weak相关底层实现用到的数据结构,主要分为SideTable,weak_table_t和weak_entry_t这几个数据结构。

struct SideTable {
    
    
    spinlock_t slock;   //锁
    RefcountMap refcnts;  //引用计数表
    weak_table_t weak_table;  //weak引用表
};  

SideTable是Objective-C中的引用计数表,它持有了weak引用表,也就是weak_table_t类型的weak_table。

struct weak_table_t {
    
    
    weak_entry_t *weak_entries;
    size_t    num_entries;  //弱引用项数量
    uintptr_t mask;         //用于计算哈希的mask
    uintptr_t max_hash_displacement;  //允许的哈希未命中的次数
};

在weak_table_t中,持有了weak_entry_t指针,这个指针指向一个数组,数组中每个weak_entry_t代表着一个弱引用项,这个数组模拟实现了hash。

struct weak_entry_t {
    
    
    DisguisedPtr<objc_object> referent;
    union {
    
    
        struct {
    
    
            weak_referrer_t *referrers;     //weak指针数组
            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];
        };
    };
};

一个弱引用项持有了referent,也就是被引用的对象,接下来使用联合体union持有了weak_referrer_t类型的数组,当weak指针指向的数组不超过4个元素时,使用定长的数组,超过4个元素的时候,使用变长数组。

这里weak_entry_t并没有直接持有被引用的对象,而是持有了DisguisedPtr类型的对象。

template <typename T>
class DisguisedPtr {
    
    
    uintptr_t value;

    static uintptr_t disguise(T* ptr) {
    
    
        return -(uintptr_t)ptr;
    }

    static T* undisguise(uintptr_t val) {
    
    
        return (T*)-val;
    }

 public:
    DisguisedPtr() {
    
     }
    DisguisedPtr(T* ptr) 
        : value(disguise(ptr)) {
    
     }
    DisguisedPtr(const DisguisedPtr<T>& ptr) 
        : value(ptr.value) {
    
     }
};

DisguisedPtr的核心方法是disguise和undisguise,disguise所做的事情是把指针的值加了负号,undisguise再次添加负号得到原始数据。做这一层包装替换的原因是防止leaks工具把对象检测为内存泄露。

整体的数据结构大致就是这样,SideTable持有weak_table_t类型的指针,weak_table_t持有

weak_entry_t类型的指针。weak_table_t代表的是weak表,里面存放很多weak指针和weak指针指向的对象,weak_entry_t代表的weak引用项,它持有了DisguisedPtr处理过的对象的指针和weak指针。weak_entry_t在底层是使用数组存储起来的,为了加快访问查找速度,在数组的数据结构上,模拟实现了hash表。

了解了weak引用相关的数据结构之后,可以看下weak实现的过程。当我们使用weak指针,使weak指针指向一个对象后,会执行到objc_storeWeak方法。

id objc_storeWeak(id *location, id newObj)
{
    
    
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}
template <HaveOld haveOld, HaveNew haveNew,
          enum 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;

 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;
    }

    if (haveNew  &&  newObj) {
    
    
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
    
    
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            //如果class还没有初始化,先进行初始化
            class_initialize(cls, (id)newObj);
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // 如果weak指针有旧值,先清理旧值
    if (haveOld) {
    
    
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 如果有新值,处理新值
    if (haveNew) {
    
    
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        

        // 设置weak引用标记位
        if (!_objc_isTaggedPointerOrNil(newObj)) {
    
    
            newObj->setWeaklyReferenced_nolock();
        }
        //把新值复制给*location,weak指针指向newObj
        *location = (id)newObj;
    }
    else {
    
    
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

objc_storeWeak函数调用了storeWeak函数,storeWeak函数中的核心方法是weak_register_no_lock。

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    
    
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    //如果是taggedPointer,直接返回referent_id
    if (_objc_isTaggedPointerOrNil(referent)) return referent_id;

    // 查看引用的对象是否在释放,如果在释放,返回nil或者crash
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
    
    
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
    
    
            deallocating = referent->rootIsDeallocating();
        }
        else {
    
    
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
    
    
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        if (deallocating) {
    
    
            if (deallocatingOptions == 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;
            }
        }
    }

    // 查找weak_entry_t,并且追加weak指针
    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_register_no_lock函数的核心在于通过weak_entry_for_referent查找弱引用项,如果已经有了对应的weak_entry_t,直接插入weak指针到数据里面。如果没有则新建new_entry对象,并且加入到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;

    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];
}

weak_entry_for_referent的实现比较简单,从weak_table->weak_entries得到weak_entry_t指针,通过

hash_pointer(referent) & weak_table->mask得到哈希的索引,如果有哈希冲突,可以增加索引值,继续查找。可以看出weak_entries实际上是数组的数据结构,为了加快查找速度,在数组上实现了hash。

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    
     
   
    if (! entry->out_of_line()) {
    
    
         //weak指针数量不超过4个
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
    
    
            if (entry->inline_referrers[i] == nil) {
    
    
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        //weak指针数量超过4个,开辟内存存储weak指针
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
       
        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;
    }

    //weak指针数量超过4个
    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;
    //找到一个为nil的位置,然后插入new_referrer
    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++;
}

append_referrer函数用于处理weak_entry_t内部的weak指针,这里也同样使用了数组模拟hash,构建了hash表存储weak指针。如果一个对象的weak指针数量不超过4个, 直接使用定长数组插入。如果超过4个,找到为nil的位置并且传入new_referrer。

以上内容是weak指针添加的过程,还有一个weak指针移除的方法。

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    
    
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;
     
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    
    
        //移除referrer指针
        remove_referrer(entry, referrer);
        //如果没有weak指针指向这个对象,需要移除weak_entry_t
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
    
    
            empty = false;
        }
        else {
    
    
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
    
    
                if (entry->inline_referrers[i]) {
    
    
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) {
    
    
            weak_entry_remove(weak_table, entry);
        }
    }
}

weak_unregister_no_lock用于移除一个weak指针,它首先查找对象对应的weak_entry_t,然后找到weak指针哈希表,移除这个weak指针,移除后要判断对象的weak指针是不是已经被清空了,如果被清空了,对应的weak_entry_t需要移除。

猜你喜欢

转载自blog.csdn.net/u011608357/article/details/128168509