# 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

posted @ 2020-10-09 12:27  阿甘左  阅读(172)  评论(0编辑  收藏  举报