iOS 相关iOSiOS面试问题集

iOS-底层原理26:weak原理

2021-03-15  本文已影响0人  AcmenL

1、weak原理概括

weak是弱引用,用weak描述修饰或者所引用对象的计数器不会加一,并且会在引用的对象被释放的时候自动被设置为nil,大大避免了野指针访问坏内存引起的崩溃情况。

weak表明该属性定义了一种“非拥有关系”,为这种属性设置新值时,设置方法既不保留新值,也不释放旧值,此特质同assign类似。

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

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

2、weak原理实现步骤

weak的实现原理可概括为三步:

1、 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

2、 添加引用时:objc_initWeak函数会调用objc_storeWeak()函数, objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表。

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

3、源码分析

step1: 可以通过打断点查看汇编的方式查看weak源码

NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;

step2: 可以在objc源码中找到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对象

step3: 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 函数把新的对象进行注册操作,完成与对应的弱引用表进行绑定操作

step4: 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,最后把这个entry从weak表中删除,最后清理对象的记录。

上一篇下一篇

猜你喜欢

热点阅读