美文网首页
iOS 内存管理(六)-weak原理

iOS 内存管理(六)-weak原理

作者: 搬砖的crystal | 来源:发表于2022-04-01 14:24 被阅读0次

一、基本用法

weak 是弱引用,用 weak 来修饰、描述所引用对象的计数器并不会增加,而且 weak 会在引用对象被释放的时候自动置为 nil,这也就避免了野指针访问坏内存而引起奔溃的情况,另外 weak 也可以解决循环引用。

runtime 如何实现 weak 变量的自动置 nil?

解答:
runtime 将 weak 对象放入一个 hash 表中,key 是所指对象的指针,valueweak 指针的地址数组(可能存在多个对象引用);
当对象的引用计数为 0 时会 dealloc,假如 weak 指向的对象内存地址为 a,那么就会以 a 为键在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil

为什么修饰代理使用 weak 而不是用 assign

assign 可用来修饰基本数据类型,也可修饰 OC 的对象,但如果用 assign 修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为 nil,否则会产生野指针,如果还通过此指针操作那块内存,会导致 EXC_BAD_ACCESS 错误,调用了已经被释放的内存空间;而 weak 只能用来修饰 OC 对象,而且相比 assign 比较安全,如果指向的对象消失了,那么它会自动置为 nil,不会导致野指针。

二、实现原理

Runtime 维护了一个 weak 表,用于存储指向某个对象的所有 weak 指针。weak 表其实是一个 hash(哈希)表,Key 是所指对象的地址,Value 是 weak 指针的地址 (这个地址的值是所指对象的地址)数组。

weak 的实现原理可以概括一下三步:
(1)初始化时:Runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
(2)添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
(3)释放时:调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entryweak 表中删除,最后清理对象的记录。

三、底层原理探索

以下案例就是使用一个临时变量 obc1 来存储创建出来的变量,代码如下:

    NSObject * objc1 = [[NSObject alloc] init];
    id  __weak objc2 = objc1;
1.objc_initWeak

当我们初始化一个 weak 变量时,runtime 会调用 NSObject.mm 中的 objc_initWeak 函数。

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

这里先判断对象是否有效,无效直接释放返回;否则通过 storeWeak 函数注册一个指向为 value__weak 对象。

2.storeWeak
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
  //声明新旧两个SideTable
    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.

//根据新值和旧值分别获取全局的SideTables表,分别赋值给oldTable,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;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(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;
}
  • 旧对象解除注册操作 weak_unregister_no_lock

该方法主要作用是将旧对象在 weak_table 中解除 weak 指针的对应绑定。根据函数名,称之为解除注册操作。从源码中,可以知道其功能就是从 weak_table 中接触 weak 指针的绑定。而其中的遍历查询,就是针对于 weak_entry 中的多张弱引用散列表。

  • 新对象添加注册操作 weak_register_no_lock

这一步与上一步相反,通过 weak_register_no_lock 函数把新的对象进行注册操作,完成与对应的弱引用表进行绑定操作

3. weak 释放为 nil 过程

weak 被释放为 nil,需要对对象整个释放过程了解,如下是对象释放的整体流程:
(1)调用 objc_release
(2)因为对象的引用计数为0,所以执行 dealloc
(3)在 dealloc 中,调用了 _objc_rootDealloc 函数
(4)在 _objc_rootDealloc 中,调用了 object_dispose 函数
(5)调用 objc_destructInstance
(6)最后调用 objc_clear_deallocating

对象准备释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址(key)获取所有weak指针地址的数组(value),然后遍历这个数组把其中的数据设为 nil,最后把这个 entryweak表中删除,最后清理对象的记录。

相关文章

网友评论

      本文标题:iOS 内存管理(六)-weak原理

      本文链接:https://www.haomeiwen.com/subject/kvlydrtx.html