Débil de gestión de memoria ios

¡Comienza el viaje de crecimiento de los Nuggets! Este es el quinto día de mi participación en el "Desafío de actualización de diciembre del nuevo plan diario de Nuggets", haga clic para ver los detalles del evento

1. tiendaProceso débil

Veamos primero un código de ejemplo:

@autoreleasepool {
        NYPerson *p = [NYPerson new];
        __weak typeof(p) weakP = p;
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));
}
复制代码

Ejecute para ver los resultados de la impresión: imagen.pngEl nuevo objeto RetainCount es 1, lo cual es fácil de entender.但是__weak修饰的weakP为什么RetainCount是2呢?

Entonces, ¿qué hace exactamente __weak debajo del capó?

Depuremos a través del punto de interrupción e ingresemos el código fuente para ver: imagen.pnga través de la impresión del punto de interrupción, sabemos que la ubicación en objc_initWeak(id *ubicación, id newObj) representa la dirección del puntero débilP, y newObj representa el objeto p NYPerson.

Luego vemos el método de núcleo real:storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj); imagen.png

  1. <DontHaveOld, DoHaveNew, DoCrashIfDeallocating> representan parámetros de plantilla.
  2. HaveOld representa si el puntero débil apunta a una referencia débil
  3. HaveNew representa si el puntero débil solo necesita apuntar a una nueva referencia débil
  4. CrashIfDeallocating representa si el objeto débilmente referenciado se está destruyendo y si se está destruyendo, se producirá un error.

Ver el código central de storeWeak:

static id 
storeWeak(id *location, objc_object *newObj)
{
    //...........................省略.............................//
    //获取两张表
    SideTable *oldTable;//oldTable
    SideTable *newTable;//newTable

 retry:
    if (haveOld) {
        oldObj = *location;//拿到old被弱引用的对象
        oldTable = &SideTables()[oldObj];//在通过这个对象获取oldTable-SideTable表
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];//在通过newObj获取newTable-SideTable表
    } else {
        newTable = nil;
    }
    
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);//设置加锁
    if (haveOld  &&  *location != oldObj) {//haveOld 存在 并且 *location指针指向的老对象 和 oldObj不相同
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); //解锁
        goto retry;//回到retry 再次判断
    }
    if (haveNew  &&  newObj) {//新的弱引用指向 和 新对象
        Class cls = newObj->getIsa();//拿到isa指向的元类
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) //判断类没有初始化
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//解锁
            class_initialize(cls, (id)newObj);//初始化 +1
            //...........................省略.............................//
            goto retry;//重新retry
        }
    }
    if (haveOld) {//如果曾指向老的对象
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);//移除在老的weak_table的数据
    }
    if (haveNew) {//如果有一个新引用
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        //添加新的引用和对象到weak_table表中
        // weak_register_no_lock returns nil if weak store should be rejected
        // Set is-weakly-referenced bit in refcount table.
        if (!_objc_isTaggedPointerOrNil(newObj)) {//判断是否是TaggedPointer
            newObj->setWeaklyReferenced_nolock();//设置newObj的weakly_referenced = true;是否被弱引用
        }
        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;//赋值到weakP指针指向的地址
    }
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//解锁
    //...........................省略.............................//
    return (id)newObj;
}
复制代码

resumen:

__weak se ejecuta en la parte inferior storeWeak函数, su función es obtener de acuerdo con la ubicación y los parámetros newObj, oldTable y newTable y luego juzgar, si el puntero débil apunta a una referencia débil antes, llamará weak_unregister_no_lockpara eliminar la dirección del puntero débil. Si el puntero débil apunta a una nueva referencia débil, llamará weak_register_no_lockpara agregar la dirección del puntero débil a la tabla de referencia débil del objeto, setWeaklyReferenced_nolockestableciéndola newisa.weakly_referenceden verdadero;

Dos, principio débil

El artículo anterior ya ha entendido la estructura y función de débil_tabla_t .

Se están haciendo algunas adiciones aquí: weak_entry_t *weak_entries;es una matriz hash que almacena información sobre objetos referenciados débilmente.

image.png 然后我们在看weak_register_no_lock函数:

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    //referent -- 被弱引用的对象
    //referrer -- weak指针的地址
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    //判断是否TaggedPointer 是返回referent_id
    if (_objc_isTaggedPointerOrNil(referent)) return referent_id;

    // 确保弱引用对象是可用的。
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
           //...........................省略.............................//
    }
    weak_entry_t *entry;
    //被弱引用的referent里面的weak_table中找到weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);// 往weak_entry_t里插入referrer -- weak指针的地址
    } 
    else {
        //没找到weak_entry_t就新建一张
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);//在把new_entry插入到weak_table中
    }
    return referent_id;
}
复制代码

weak_register_no_lock添加弱引用函数流程:

  • 如果被弱引用的对象为nil或这是一个TaggedPointer,直接返回,不做任何操作。
  • 如果被弱引用的对象正在析构,则抛出异常。
  • 如果被弱引用的对象不能被weak引用,直接返回nil。
  • 如果对象没有再析构并且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry_t,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry_t。

我们在看weak_unregister_no_lock函数:

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    //referent -- 被弱引用的对象
    //referrer -- weak指针的地址
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    weak_entry_t *entry;
    // 被弱引用的对象 ,不存在返回
    if (!referent) return;
    // 被弱引用的referent里面的weak_table中找到weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        remove_referrer(entry, referrer);//从weak_entry_t 中移除weak指针的地址
        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;
                }
            }
        }
        // 4张表中都不存在referrer 指针的地址,并且entry 中已经weak指针已被移除
        if (empty) {
            weak_entry_remove(weak_table, entry);//移除weak_table中的weak_entry_t
        }
    }
}
复制代码

weak_unregister_no_lock移除弱引用函数流程:

  • referent被弱引用的对象 ,不存在直接返回。
  • 通过weak_entry_for_referent方法在weak_table中找出被弱引用对象对应的weak_entry_t
  • 在weak_entry_t中移除weak指针的地址。
  • 移除元素后,判断此时weak_entry_t中是否还有元素,如果此时weak_entry_t已经没有元素了,则需要将weak_entry_t从weak_table中移除。

整理了一个对象sidetable,weak关系图: image.png

三、weak引用计数问题

重新回到例子1的问题,但是__weak修饰的weakP为什么RetainCount是2呢?

我们开始断点调试,看看CFGetRetainCount弱引用和强引用有什么区别。

查看强引用汇编:

image.png 查看弱引用汇编: image.png 看到weakPCFGetRetainCount前会执行objc_loadWeakRetained这个函数。

然后我们搜索objc_loadWeakRetained这个函数进入源码:

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;
    SideTable *table;

 retry:
    // fixme std::atomic this load
    obj = *location;//获取弱引指针指向的对象
    if (_objc_isTaggedPointerOrNil(obj)) return obj;//如果是TaggedPointer直接返回
    table = &SideTables()[obj];//获取对象的SideTable
    table->lock();//加锁
    if (*location != obj) {//指针指指向的对象和obj不相等
        table->unlock();//解锁
        goto retry;
    }
    result = obj;
    cls = obj->ISA();//得到对象的ISA
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        if (! obj->rootTryRetain()) {//rootTryRetain->rootRetain  这里加1了
            result = nil;
        }
    }
    else {
    //...........................省略.............................//
    }
    table->unlock();
    return result;//局部变量 在arc中会-1
}
复制代码

继续断点控制台打印: image.png 我们在看一个情况:

        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));

        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));

        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
复制代码

Mire el resultado de la impresión: image.png¿Por qué el recuento de referencias de obj es +1 después de pasar por la objc_loadWeakRetainedfunción y encontrarlo obj->rootTryRetain()en el código fuente, pero el recuento de referencias de la fuente p sigue contando 1?

Debido a que el objc_loadWeakRetainedresultado en la función es una variable local en arco, se decrementará en 1 después de la ejecución, por lo tanto, no afectará el recuento de referencia del objeto p externo.

Resumir

  1. storeWeak: __weak se implementa en la parte inferior storeWeak函数, su función es obtener de acuerdo con la ubicación y los parámetros newObj, y luego juzgar oldTable y newTable (ambos son tipos de sideTable).
    • Si el puntero débil apuntaba anteriormente a una referencia débil, se llamará weak_unregister_no_lockpara eliminar la dirección del puntero débil.
    • Si el puntero débil apunta a una nueva referencia débil, llamará weak_register_no_lockpara agregar la dirección del puntero débil a la tabla de referencia débil del objeto .
  2. weak原理: Es encontrar la tabla débil relevante a través de la tabla lateral del objeto. Las weak_unregister_no_lockdos weak_register_no_lockfunciones agregan y eliminan la dirección del puntero débil.
    • weak_unregister_no_lock: si el objeto no se vuelve a destruir y puede ser referenciado por débil, el weak_entry_for_referentmétodo de llamada encuentra la entrada débil correspondiente de la tabla de referencia débil de acuerdo con la dirección del objeto de referencia débil y, si se puede encontrar, el append_referrermétodo de llamada inserta el dirección del puntero débil en él. De lo contrario, cree una nueva entrada débil_t.
    • weak_register_no_lock: utilice el weak_entry_for_referentmétodo para encontrar el objeto de referencia débil correspondiente weak_entry_ten la tabla débil y elimine la dirección del puntero débil en la entrada débil.
  3. weak引用计数问题: Hay uno en la función CFGetRetainCount((__bridge CFTypeRef)(weakP))ejecutada antes de imprimir , y luego el conteo de referencia es +1. Y no afecta el recuento de referencias del objeto p externo.objc_loadWeakRetainedrootTryRetain()->rootRetain()

Supongo que te gusta

Origin juejin.im/post/7176168914060050492
Recomendado
Clasificación