欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OC对象之旅 weak弱引用实现分析

程序员文章站 2022-06-02 12:54:07
...

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

OC对象之旅 weak弱引用实现分析

两种常见使用场景

/// weak属性
@interface XX : XX
@property(nonatomic,weak) Type* weakPtr;
@end

/// 代码块中使用
{
    /// 使用__weak
    __weak Type* weakPtr = [[SomeObject alloc] init];
}

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

OC对象之旅 weak弱引用实现分析

    /** 
    * 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);
    }

OC对象之旅 weak弱引用实现分析

    /** 
    * 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);
    }

OC对象之旅 weak弱引用实现分析

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

OC对象之旅 weak弱引用实现分析

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

OC对象之旅 weak弱引用实现分析

重点来了:

/// 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.");
    }

    ///锁
    ....
};

OC对象之旅 weak弱引用实现分析

    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个

OC对象之旅 weak弱引用实现分析

后三个参数不用解释,主要解释第一个参数,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中

OC对象之旅 weak弱引用实现分析

定义中我们重点关注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释义如下

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

新建

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

OC对象之旅 weak弱引用实现分析

调整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中,同时更新相关计数器。 上面过程的流程如下:

OC对象之旅 weak弱引用实现分析
OC对象之旅 weak弱引用实现分析

消除弱引用

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

文章转载自 开源中国社区[https://www.oschina.net]