[El principio subyacente de iOS débil]

1. Mesas auxiliares

Referencia de: Gestión de memoria de iOS (3) Explicación detallada de SideTables. Las SideTables
están estrechamente relacionadas con la gestión de memoria de iOS. Hoy, estudiemos las SideTables. Primero, echemos un vistazo a la definición de SideTables.

static StripedMap<SideTable>& SideTables() {
    
    
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

El tipo real de SideTables es el StripeMap que almacena SideTbale . En la clase StripeMap, StripeCount define el número máximo de tablas laterales que se almacenarán, por lo que cada SideTables puede corresponder a múltiples objetos y cada objeto corresponde a una tabla lateral.

1.1 Mapa rayado

Por lo anterior sabemos que SideTables es en realidad un depósito de hash global y devuelve una referencia de tipo StripeMap&:

static StripedMap<SideTable>& SideTables() {
    
    
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
template<typename T>
class StripedMap {
    
    
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum {
    
     StripeCount = 8 };
#else
    enum {
    
     StripeCount = 64 };
#endif

    struct PaddedT {
    
    
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];
    ...
}

1 StripeCount define el número máximo de objetos almacenados en él.

enum {
    
     StripeCount = 8 };

2. Defina la estructura QuiltT para envolver el genérico entrante (aquí se refiere a SideTable) y use el método alignas (CacheLineSize) para alinear los bytes. El propósito de adivinar la alineación de bytes es mejorar la eficiencia del acceso a los valores hash.

struct PaddedT {
    
    
    T value alignas(CacheLineSize);
};

3. Implementó el algoritmo hash indexForPointer para el cálculo del índice.

static unsigned int indexForPointer(const void *p) {
    
    
    uintptr_t addr = reinterpret_cast<uintptr_t>(p);
    return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}

4. Obtenga la operación método getLock de la tabla lateral

const void *getLock(int i) {
    
    
    if (i < StripeCount) return &array[i].value;
    else return nil;
}

5. Otras operaciones de bloqueo, array[i].value.lock o array[i].value.unlock(), llaman al bloqueo en la tabla lateral.

void lockAll() {
    
    
    for (unsigned int i = 0; i < StripeCount; i++) {
    
    
        array[i].value.lock();
    }
}

Es decir, el StripeMap devuelto por SideTables () es un depósito de hash cuyo valor es SideTable (dado que SideTable mantiene una matriz internamente, esta es una estructura de depósito de hash), y el valor hash se calcula a partir de la dirección del objeto.

El diagrama de estructura de SideTables se muestra a continuación:
Insertar descripción de la imagen aquí

1.2 Mesa auxiliar

SideTable contiene tres miembros, bloqueo de giro, tabla de recuento de referencias y tabla de referencia débil.

- spinlock_t slock; // 保证原子操作的自旋锁
- RefcountMap refcnts; // 引用计数的hash 表
- weak_table_t weak_table; 

Se utiliza principalmente para gestionar recuentos de referencias y tablas de objetos débiles.

  • slock: Un bloqueo de giro seleccionado para evitar la competencia.
  • refcnts: Una tabla utilizada para almacenar el recuento de referencia de los objetos OC hash(solo se usa cuando la optimización isa no está activada o el recuento de referencia de isa_t se desborda bajo la optimización isa).
  • weak_table: una tabla hash que almacena punteros de referencia débiles a objetos. Es la estructura de datos central para realizar la función débil en OC.

bloqueo

  • slock es un bloqueo de giro. Para garantizar la seguridad del acceso multiproceso: bloquee SideTable cuando opere el recuento de referencias para evitar errores de datos.
  • ⚠️: La esencia del slock aquí es un bloqueo giratorio. Agregamos un bloqueo a cada SideTable para evitar que se acceda a una determinada SideTable varias veces. Este es un bloqueo independiente.

refcntes

  • Esencialmente, es una tabla hash que almacena el recuento de referencias de objetos. La clave es el objeto y el valor es el recuento de referencia (se ha optimizado en isa y el recuento de referencia se almacena principalmente en isa).

Insertar descripción de la imagen aquí

tabla_débil

  • Es una estructura que almacena referencias débiles a objetos, los miembros de la estructura son los siguientes
- weak_entry_t *weak_entries;
- size_t    num_entries;
- uintptr_t mask;
- uintptr_t max_hash_displacement;

1.3 Estructura de almacenamiento de refcnts de recuento de referencias RefcountMap

RefcountMap se define de la siguiente manera, su tipo es objc::DenseMap.

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

Los tres parámetros representan respectivamente la clave hash y el recuento de referencias del objeto , y si el nodo hash correspondiente debe liberarse automáticamente cuando el recuento de referencias llegue a 0. True se pasa aquí de forma predeterminada.

Por lo tanto, el recuento de referencias y los refcnts del objeto no existen necesariamente: solo el isa optimizado usa extea_rc para almacenar el recuento de referencias, y solo cuando su recuento de almacenamiento se desborde se almacenarán los refcnts de la tabla lateral.

1.4 estructura débil_table_t

/**
   全局的弱引用表, 保存object作为key, weak_entry_t作为value
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    
    
    // 保存了所有指向特地对象的 weak指针集合
    weak_entry_t *weak_entries;
    // weak_table_t中有多少个weak_entry_t
    size_t    num_entries;
    // weak_entry_t数组的count
    uintptr_t mask;
    // hash key 最大偏移值, 
    // 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_table_t中不存在要找的weak_entry_t
    uintptr_t max_hash_displacement;
};

  • weak_entries:hashMatriz, utilizada para almacenar información sobre objetos de referencia débiles débiles_entry_t.
  • num_entries:hashEl número de elementos de la matriz.
  • mask:hashLongitud de la matriz -1, participará en el cálculo del hash. (Tenga en cuenta que esta es la longitud de la matriz hash, no el número de elementos. Por ejemplo, la longitud de la matriz puede ser 64, pero la cantidad de elementos es solo 2).
  • max_hash_displacement: El número máximo de conflictos hash que pueden ocurrir, utilizado para determinar si se ha producido un error lógico (el número de conflictos en la tabla hash nunca excederá el valor modificado).

weak_table_tes una hashestructura típica. weak_entriesEs una matriz dinámica que se utiliza para almacenar weak_entry_telementos de un tipo, que en realidad son información de referencia débil de los objetos OC.

1.4.1 Estructura .weak_entry_t

weak_entry_tLa estructura de también es una hashestructura, y sus elementos almacenados son punteros a punteros de objetos con referencias débiles.Al manipular el puntero del puntero, se puede señalar el puntero al que se hace referencia weakdespués de que se destruye el objeto nil. El código de implementación es el siguiente:

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2

typedef objc_object ** weak_referrer_t;

struct weak_entry_t {
    
    
    // 所有weak指针指向的特定对象
    DisguisedPtr<objc_object> referent; // 被弱引用的对象
    // 共用体,保存weak指针的集合, 
    // 引用个数小于4,用inline_referrers数组。用个数大于4,用动态数组weak_referrer_t *referrers
    union {
    
    
        struct {
    
    
            weak_referrer_t *referrers;  // 弱引用该对象的对象指针地址的hash数组
            uintptr_t        out_of_line : 1;  // 是否使用动态hash数组标记位
            uintptr_t        num_refs : PTR_MINUS_1;  // hash数组中的元素个数
            uintptr_t        mask;  // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。
            uintptr_t        max_hash_displacement;  // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
        };
        struct {
    
    
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    }
}

En weak_entry_tla estructura, DisguisedPtr referentel puntero del objeto genérico está encapsulado y el problema de la pérdida de memoria se resuelve a través de esta clase genérica. Además, hay una tabla dentro weak_entry_ty weak_table_tfuera hash, y los conflictos hash se resuelven utilizando el método de valor abierto. El miembro se escribe como el bit menos significativo del comentario out_of_line. Cuando es 1, weak_referrer_tel miembro se expandirá a hash table. El débil_referrer_t es un alias de una matriz.

  • out_of_line: Bit de bandera. Marca si débil_entry_t se guarda en una matriz o tabla hash para guardar el puntero débil.
  • num_refs: Recuento de referencias. Aquí se registra el número de punteros débiles en la tabla débil_entry_t.
  • mask: débil_entry_t->recuento de matriz de referencias.
  • max_hash_displacement: El valor de desplazamiento máximo de la clave hash, utilizando el método de personalización abierto para resolver conflictos de hash. Si excede max_hash_displacement, significa que el débil_entry_t que está buscando no existe en débil_entry_t.

El out_of_linevalor suele ser igual a cero, por lo que la tabla de referencia débil siempre es una objc_objectivematriz de punteros. Cuando supera 4, se convierte en una tabla hash.

La estructura de débil es como se muestra en la figura.
Insertar descripción de la imagen aquí

2.1 Implementación de funciones débiles y relacionadas llamadas

  1. Concepto básico: Basado en nuestra experiencia anterior: débil es una referencia débil, el contador del objeto referenciado no aumentará en uno y se establecerá automáticamente en cero cuando se libere el objeto referenciado.
  2. Principio de implementación: Runtime mantiene una tabla débil para almacenar todos los punteros débiles que apuntan a un objeto. La tabla débil es en realidad una tabla hash, la clave es la dirección del objeto puntiagudo y el valor es la dirección del puntero débil (el valor de esta dirección es la dirección del puntero del objeto puntiagudo, que es la dirección de la dirección) La colección (cuando el puntero débil es menor o igual a 4, es una matriz, y cuando excede, se convierte en una tabla hash).

El principio de implementación de débil se resume en los siguientes tres pasos:
1. Durante la inicialización: runtimese llamará a una función objc_initWeakpara inicializar un nuevo puntero débil que apunta a la dirección del objeto.
2. Al agregar una referencia: objc_initWeakla función llamará objc_storeWeaka la función (), objc_storeWeak()que se utiliza para actualizar el puntero y crear la tabla de referencia débil correspondiente.
3. Cuando se libere, llame clearDeallocatinga la función. clearDeallocatingLa función primero obtiene weakuna matriz de todas las direcciones de puntero según la dirección del objeto, luego atraviesa la matriz y establece los datos que contiene nil, y finalmente los elimina entryde weakla tabla y limpia los registros del objeto.

2.1 Durante la inicialización:

1. Durante la inicialización:

runtimeSe llamará a la función objc_initWeaky objc_initWeakla función inicializará un nuevo weakpuntero que apunta a la dirección del objeto.

2.1.1 método objc_initWeak

El código fuente subyacente del método objc_initWeak es el siguiente:

// location指针objc , newObj原始对象object
id objc_initWeak(id *location, id newObj) {
    
    
	// 查看原始对象实例是否有效
	// 无效对象直接导致指针释放
    if (!newObj) {
    
    
        *location = nil;
        return nil;
    }
    // 这里传递了三个 bool 数值
    // 使用 template 进行常量参数传递是为了优化性能
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}

Este método tiene dos parámetros locationy newObj.

  • location: __weakLa dirección del puntero, que almacena la dirección del puntero para que el objeto al que apunta se pueda establecer en cero al final.
  • newObj: El objeto al que se hace referencia. Ese es el objeto en el ejemplo.

Del código anterior, podemos ver que objc_initWeakel método es solo la entrada a una llamada de función profunda, y el método se llama dentro del storeWeakmétodo. Echemos un vistazo al storeWeakcódigo de implementación del método.

Nota⚠️: objc_initWeakLa función tiene un requisito previo: objectdebe ser un __weakpuntero válido que no haya sido registrado como objeto. Y valuepuede ser nilo apuntar a un objeto válido.

2.1.2 método storeWeak

El código fuente subyacente del método storeWeak es el siguiente:

// HaveOld:  true - 变量有值
//          false - 需要被及时清理,当前值可能为 nil
// HaveNew:  true - 需要被分配的新值,当前值可能为 nil
//          false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
//          false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    
    
    // 该过程用来更新弱引用指针的指向
    // 初始化 previouslyInitializedClass 指针
    Class previouslyInitializedClass = nil;
    id oldObj;
    // 声明两个 SideTable
    // ① 新旧散列创建
    SideTable *oldTable;
    SideTable *newTable;
    // 获得新值和旧值的锁存位置(用地址作为唯一标示)
    // 通过地址来建立索引标志,防止桶重复
    // 下面指向的操作会改变旧值
retry:
	// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable,即获取其旧的Table
    if (HaveOld) {
    
    
        // 更改指针,获得以 oldObj 为索引所存储的值地址
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
    
      // 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
        oldTable = nil;
    }
    // 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
    if (HaveNew) {
    
    
        // 更改新值指针,获得以 newObj 为索引所存储的值地址
        newTable = &SideTables()[newObj];
    } else {
    
      // 如果weak ptr不需要引用一个新obj,则newTable = nil
        newTable = nil;
    }
    // 加锁操作,防止多线程中竞争冲突
    SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
    // 避免线程冲突重处理
    // location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改,需要返回上边重新处理
    if (HaveOld  &&  *location != oldObj) {
    
    
        SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
        goto retry;
    }
    // 防止弱引用间死锁
    // 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
    if (HaveNew  &&  newObj) {
    
    
        // 获得新对象的 isa 指针
        Class cls = newObj->getIsa();
        // 如果cls还没有初始化,先初始化,再尝试设置weak
        if (cls != previouslyInitializedClass  &&
            !((objc_class *)cls)->isInitialized()) {
    
    
            // 解锁
            SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
            // 对其 isa 指针进行初始化
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            // 如果该类已经完成执行 +initialize 方法是最理想情况
            // 如果该类 +initialize 在线程中
            // 例如 +initialize 正在调用 storeWeak 方法
            // 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记,防止改if分支再次进入
            previouslyInitializedClass = cls;
            // 重新获取一遍newObj,这时的newObj应该已经初始化过了
            goto retry;
        }
    }
    // ② 清除旧值
    //  如果之前该指针有弱引用过一个obj那就得需要清除之前的弱引用
    if (HaveOld) {
    
    
    	// 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // ③ 分配新值
    // 如果weak_ptr需要弱引用新的对象newObj
    if (HaveNew) {
    
    
    	// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
    	// 如果弱引用被释放 weak_register_no_lock 方法返回 nil
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
                                                      (id)newObj, location,
                                                      CrashIfDeallocating);
        // (2) 更新newObj的isa的weakly_referenced bit标志位
        if (newObj  &&  !newObj->isTaggedPointer()) {
    
    
            // 弱引用位初始化操作
            // 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
            newObj->setWeaklyReferenced_nolock();
        }
        // (3)*location 赋值,也就是将weak ptr直接指向了newObj,也就是确保其指针指向是正确的。可以看到,这里并没有将newObj的引用计数+1
        *location = (id)newObj;
    }
    else {
    
    
        // 没有新值,则无需更改
    }
    // 解锁,其他线程可以访问oldTable, newTable了
    SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
    // 返回newObj,此时的newObj与刚传入时相比,设置了weakly-referenced bit位置1
    return (id)newObj;
}

El método storeWeak hace lo siguiente:

  1. storeWeakEn realidad, el método recibe 5 parámetros, a saber haveOld, , haveNewy crashIfDeallocating. Estos tres parámetros se pasan en forma de plantillas y son tres parámetros de tipo bool. Indican respectivamente weaksi el puntero apuntaba a una referencia débil antes, si el puntero débil necesita apuntar a una nueva referencia y, si el objeto con referencia débil se está destruyendo, si el objeto con referencia débil debería fallar en este momento. Además id *location,objc_object *newObj
  2. Este método mantiene oldTabley newTablerepresenta la antigua tabla de referencia débil y la nueva tabla de referencia débil respectivamente, las cuales son SideTabletablas hash.
  3. Si el puntero débil anteriormente apuntaba a una referencia débil, weak_unregister_no_lockse llamará a un método para eliminar la weakdirección del puntero anterior.
  4. Si el puntero débil necesita apuntar a una nueva referencia, weak_register_no_lockse llamará a un método para agregar la nueva weakdirección del puntero a la tabla de referencia débil.
  5. Llame setWeaklyReferenced_nolockal método para modificar weakel indicador de bits del objeto recién referenciado.

Entonces, el enfoque de este método es weak_unregister_no_lockestos weak_register_no_lockdos métodos. Ambos métodos operan sobre SideTablevariables de dicha estructura.

2.2 Al agregar una referencia:

objc_initWeakLa función llamará a objc_storeWeak()la función, y objc_storeWeakla función de () es actualizar el puntero y crear la tabla de referencia débil correspondiente.

2.2.1 método débil_register_no_lock

Agregue una operación de registro para un nuevo objeto weak_register_no_lock, weak_register_no_lockregistre el nuevo objeto a través de una función y complete la operación de enlace con la tabla de referencia débil correspondiente.

El código de implementación es el siguiente:

/*	weak_table:weak_table_t结构类型的全局的弱引用表。
	referent_id:weak指针所指的对象。
	*referrer_id:weak修饰的指针的地址。
	crashIfDeallocating:如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
*/
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;
 
    // 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;
 
    // 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
    
    
        deallocating = referent->rootIsDeallocating();
    }
    else {
    
      //不能被weak引用,直接返回nil
        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_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    
     // 如果能找到weak_entry,则讲referrer插入到weak_entry中
        append_referrer(entry, referrer);     // 将referrer插入到weak_entry_t的引用数组中
    }
    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;
}

débil_register_no_lock funciona de la siguiente manera:

  • Si referentestá nilo referentusa TaggedPointerel modo de conteo, regresa directamente sin ninguna operación.
  • Si no se puede hacer referencia al objeto weak, devuélvalo directamente nil.
  • Si el objeto se destruye, se lanza una excepción.
  • Si el objeto no se destruye y se puede hacer referencia a él weak, el weak_entry_for_referentmétodo de llamada busca el objeto de referencia débil correspondiente en la tabla de referencia débil de acuerdo con la dirección del objeto de referencia débil weak_entry. Si se puede encontrar, el método de llamada inserta la dirección del puntero append_referreren él. . weakDe lo contrario, cree uno nuevo weak_entry.
débil_entry_for_referent toma el método del elemento y append_referrer agrega el elemento

débil_entry_for_referent toma elementos

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;  // 这里通过 & weak_table->mask的位操作,来确保index不会越界
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
    
    
        index = (index+1) & weak_table->mask;
        // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
        if (index == begin) bad_weak_table(weak_table->weak_entries); // 触发bad weak table crash
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
    
     // 当hash冲突超过了可能的max hash 冲突时,说明元素没有在hash表中,返回nil
            return nil;
        }
    }
 
 	//返回找到的元素
    return &weak_table->weak_entries[index];
}

.append_referrer agrega elementos

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    
    
	// 如果weak_entry 尚未使用动态数组,走这里
    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;
            }
        }
 
        // 如果inline_referrers的位置已经存满了,则要转型为referrers,做动态数组。
        // 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()); // 断言:此时一定使用的动态数组
 	// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
    
     
    	// 扩容,并插入
        return grow_refs_and_insert(entry, new_referrer);
    }
 
    // 如果不需要扩容,直接插入到weak_entry中
    // 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer
 
    // 细心的人可能注意到了,这里weak_entry_t 的hash算法和 weak_table_t的hash算法是一样的,同时扩容/减容的算法也是一样的
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 确保了 begin的位置只能大于或等于 数组的长度
    size_t index = begin;  // 初始的hash index
    size_t hash_displacement = 0;  // 用于记录hash冲突的次数,也就是hash再位移的次数
    //  使用循环找到一个合适的空位
    while (entry->referrers[index] != nil) {
    
    
        hash_displacement++;
        index = (index+1) & entry->mask;  // index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)
        if (index == begin) bad_weak_table(entry); // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
    }
    // 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
    if (hash_displacement > entry->max_hash_displacement) {
    
     
        entry->max_hash_displacement = hash_displacement;
    }
    // 将值插入刚才找到的hash表的空位,同时,更新元素个数num_refs
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

2.2.2 débil_unregister_no_lock elimina referencias

Si el puntero débil anteriormente apuntaba a una referencia débil, weak_unregister_no_lockse llamará a un método para eliminar la weakdirección del puntero anterior.

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
                        id *referrer_id)
{
    
    
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
 
    weak_entry_t *entry;
 
 	//  弱引用对象为nil不存在,直接返回
    if (!referent) return;
 
 	// 查找到referent所对应的weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    
     
        remove_referrer(entry, referrer);  // 在referent所对应的weak_entry_t的hash数组中,移除referrer
 
        // 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
        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;
                }
            }
        }
 
 		// 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
        if (empty) {
    
    
            weak_entry_remove(weak_table, entry);
        }
    }
    return;
}

El método débil_unregister_no_lock hace lo siguiente al eliminar la referencia:

  • referent(弱引用对象)·Primero, encontrará la entrada débil correspondiente en la tabla débil .
  • weak_entry_tEliminado en referrer.
  • Después de eliminar el elemento, determine weak_entry_tsi todavía hay elementos en este momento (vacío == ¿verdadero?).
  • Si weak_entry_tno quedan elementos en este momento, es weak_entry_tnecesario weak_table.

Hasta ahora, esto es lo que se hace en la parte inferior al hacer una referencia débil a un objeto. Después de usar una referencia débil al objeto, el recuento de referencias no aumentará en 1. Cuando se libera el objeto, ¿cómo están todos los punteros que ¿Referencias débiles se establecen automáticamente en cero?

3.1 Cuando se publique:

Llame clearDeallocatinga la función. clearDeallocatingLa función primero obtiene una matriz de todas weaklas direcciones de puntero según la dirección del objeto, luego atraviesa la matriz y establece los datos que contiene nil, finalmente los elimina entryde weakla tabla y finalmente borra los registros del objeto.

3.1.1 Cuando se libera el objeto señalado por la referencia débil, ¿cómo se maneja el puntero débil?

Cuando se libera un objeto, el proceso básico es el siguiente:

1. Llame objc_release.
2. Debido a que el recuento de referencias del objeto es 0, ejecute dealloc.
3. En dealloc, la función se llama _objc_rootDealloc.
4. En _objc_rootDealloc, se llama a la función object_dispose.
5. Llame objc_destructInstance.
6. Convocatoria final objc_clear_deallocating.

3.1.2 método de desasignación

Cuando el recuento de referencias del objeto es 0, _objc_rootDeallocse llamará al método subyacente para liberar el objeto y _objc_rootDeallocse llamará al método dentro del método rootDealloc. La siguiente es rootDeallocla implementación del código del método:

inline void
objc_object::rootDealloc()
{
    
    
	//  判断对象是否是Tagged Pointer,如果是则直接返回。
    if (isTaggedPointer()) return;  // fixme necessary?
 
 	//如果对象是采用了优化的isa计数方式,且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C++析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。
    if (fastpath(isa.nonpointer  &&
                 !isa.weakly_referenced  &&
                 !isa.has_assoc  &&
                 !isa.has_cxx_dtor  &&
                 !isa.has_sidetable_rc))
    {
    
    
        assert(!sidetable_present());
        free(this);
    }
    else {
    
      // 调用object_dispose方法。
        object_dispose((id)this);
    }
}

Esta función también involucra object_disposemétodos

3.1.3 método object_dispose

object_disposeobjc_destructInstanceEl método es simple, principalmente porque el método se llama internamente.

void *objc_destructInstance(id obj) 
{
    
    
    if (obj) {
    
    
        // Read all of the flags at once for performance.
        //  是否有自定义的C++析构方法
        bool cxx = obj->hasCxxDtor();
        //  是否有关联对象
        bool assoc = obj->hasAssociatedObjects();
 
        // This order is important.
        //  如果有自定义的C++析构方法,则调用C++析构函数。
        if (cxx) object_cxxDestruct(obj);
        //  如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
 
    return obj;
}

3.1.4 Método .clearDeallocating
inline void 
objc_object::clearDeallocating()
{
    
    
	//  判断对象是否采用了优化isa引用计数
    if (slowpath(!isa.nonpointer)) {
    
    
        // Slow path for raw pointer isa.
        //  如果没有的话则需要清理对象存储在SideTable中的引用计数数据
        sidetable_clearDeallocating();
    }
    //  如果对象采用了优化isa引用计数,则判断是否有使用weak引用(isa.weakly_referenced)或者有使用SideTable的辅助引用计数(isa.has_sidetable_rc),符合这两种情况中一种的,调用clearDeallocating_slow方法。
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
    
    
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }
 
    assert(!sidetable_present());
}

3.1.5 Método .clearDeallocating_slow
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    
    
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
 
 	// 在全局的SideTables中,以this指针为key,找到对应的SideTable
    SideTable& table = SideTables()[this];
    //  上锁
    table.lock();
    // 如果obj被弱引用
    if (isa.weakly_referenced) {
    
    
        // 在SideTable的weak_table中对this进行清理工作
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 如果采用了SideTable做引用计数
    if (isa.has_sidetable_rc) {
    
    
        // 在SideTable的引用计数中移除this
        table.refcnts.erase(this);
    }
    //  解锁
    table.unlock();
}

Lo que nos interesa aquí es weak_clear_no_lockel método. weak_clear_no_lockEl trabajo de limpieza se llama aquí weak_table.

3.1.6 método débil_clear_no_lock
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    
    
    objc_object *referent = (objc_object *)referent_id;
 
 	// 找到referent在weak_table中对应的weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
    
    
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }
 
    // zero out references
    weak_referrer_t *referrers;
    size_t count;
 
    // 找出weak引用referent的weak 指针地址数组以及数组长度
    if (entry->out_of_line()) {
    
      //  如果是动态数组
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    }
    else {
    
      //  如果是静态数组
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
 
    for (size_t i = 0; i < count; ++i) {
    
    
    	// 取出每个weak ptr的地址
        objc_object **referrer = referrers[i];
        if (referrer) {
    
    
        	// 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
            if (*referrer == referent) {
    
    
                *referrer = nil;
            }
            else if (*referrer) {
    
     // 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n",
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
 	// 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
    weak_entry_remove(weak_table, entry);
}

objc_clear_deallocatingLa acción de esta función es la siguiente:

1. Desde la tabla débil, use el objeto dealloc como clave para encontrar el débil_entry_t correspondiente.
2. Asigne todas las direcciones en débil_entry_t con variables modificadoras débiles a cero.
3. Retire el objeto de la mesa débil.

Resumir

  1. weakEl principio es que la capa inferior mantiene una weak_table_ttabla hash de estructura, cuya clave es la dirección del objeto señalado y valuela weakmatriz de direcciones del puntero.
  2. weakLa función de la palabra clave es una referencia débil. El contador del objeto referenciado no aumentará en 1 y se establecerá automáticamente cuando se libere el objeto referenciado nil.
  3. Cuando se libera el objeto, la clearDeallocatingfunción de llamada obtiene la matriz de todas las direcciones del puntero según la dirección del objeto weak, luego atraviesa la matriz para configurar los datos que contiene nil, finalmente elimina esto entryde weakla tabla y finalmente limpia los registros del objeto.
  4. El artículo presenta SideTable、weak_table_t、weak_entry_testas tres estructuras y la relación entre ellas se muestra en la siguiente figura.
    Insertar descripción de la imagen aquí
  5. Para la inicialización de referencias débiles, del análisis anterior se puede ver que la parte principal de la operación es obtener el valor de la tabla de referencia débil, consultar la secuencia, crear la tabla de referencia débil y otras operaciones, que se resumen a continuación.

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_61639290/article/details/131920032
Recomendado
Clasificación