[iOS] Principio de implementación de palabras clave débiles

Prefacio

Respecto a qué son las palabras clave débiles, puedes leer mi blog anterior: [OC] Palabras clave de atributo

principio débil

1. Mesa auxiliar

A la estructura SideTable, los predecesores le dieron un nombre muy vívido llamado recuento de referencias y tabla de dependencia de referencias débiles, porque se utiliza principalmente para gestionar el recuento de referencias y la tabla de dependencias débiles de objetos. Declare su estructura de datos en NSObject.mm:

struct SideTable {
    
    
// 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
    
    SideTable() {
    
    
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
    
    
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() {
    
     slock.lock(); }
    void unlock() {
    
     slock.unlock(); }
    void reset() {
    
     slock.reset(); }

    // 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);
}

slock es un bloqueo de giro seleccionado para evitar la competencia.
refcnts es una variable que ayuda al recuento de referencia común extra_rc del puntero isa del objeto (para los resultados del objeto, se menciona más adelante)

A continuación, echemos un vistazo a estas tres variables miembro en SideTable:

1.1 spinlock_t slock bloqueo de giro

Los bloqueos de giro son más eficientes que los bloqueos mutex. Sin embargo, debemos tener en cuenta que dado que la CPU no se libera durante el giro, el hilo que sostiene el bloqueo de giro debe liberarlo lo antes posible; de ​​lo contrario, el hilo que espera el bloqueo de giro seguirá girando, lo que desperdiciará tiempo de la CPU.

Bloquee SideTable cuando opere el conteo de referencias para evitar errores de datos.

1.2 Mapa de recuento

Insertar descripción de la imagen aquí

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

Entre ellos, DenseMap es otra clase de plantilla:

template<typename KeyT, typename ValueT,
         bool ZeroValuesArePurgeable = false, 
         typename KeyInfoT = DenseMapInfo<KeyT> >
class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, 
  ZeroValuesArePurgeable, KeyInfoT>, KeyT, ValueT, KeyInfoT, 
  ZeroValuesArePurgeable> {
    
    
  ...
  BucketT *Buckets;
  unsigned NumEntries;
  unsigned NumTombstones;
  unsigned NumBuckets;
  ...
}

Los miembros más importantes incluyen los siguientes:

1.El ZeroValuesArePurgeablevalor predeterminado es false, pero RefcountMapespecifique su inicialización como true. Si esta etiqueta de miembro puede usar un depósito con un valor de 0 (recuento de referencias de 1). Debido a que el valor inicial de un depósito vacío es 0, no hay diferencia entre un depósito con un valor de 0 y un depósito vacío. Si se permite el uso del depósito con un valor de 0, si no se encuentra el depósito correspondiente al objeto al buscar un depósito y no se encuentra el depósito de lápida, se utilizará primero el depósito con un valor de 0.
2. BucketsLos punteros administran un espacio de memoria continuo, es decir, una matriz. Los miembros de la matriz son BucketTobjetos de tipo. BucketTAquí llamamos a los objetos cubos (en realidad, esta matriz debería llamarse cubos. Apple llama a los elementos de la matriz cubos por el bien de imagen, algo, no en el sentido de cubos dentro de cubos de hash). Después de que la matriz de depósitos solicite espacio, se inicializará y los depósitos vacíos se colocarán en todas las posiciones (cuando el depósito keyes , EmptyKeyes un depósito vacío). Las operaciones de recuento de referencias posteriores dependerán del depósito.
El tipo de datos del depósito es en realidad std::pairsimilar al swifttipo ancestral en, es decir, la dirección del objeto y el recuento de referencia del objeto (el recuento de referencia aquí es similar a isa, y varios de ellos se utilizan para guardar el recuento de referencia, y algunos están bitreservadosbit

BucketTse define de la siguiente manera:

typedef std::pair<KeyT, ValueT> BucketT;

3. NumEntriesRegistre la cantidad de depósitos no vacíos usados ​​en la matriz.

4. NumTombstones, Tombstonetraducido literalmente como lápida, cuando el recuento de referencia de un objeto es 0 y se va a sacar del cubo, se marcará su ubicación. Es el Tombstonenúmero NumTombstonesde lápidas en la matriz. El papel de las lápidas será introducido más tarde.

5. NumBucketsEl número de depósitos, debido a que la matriz siempre está llena de depósitos, puede entenderse como el tamaño de la matriz.

inline uint64_t NextPowerOf2(uint64_t A) {
    
    
    A |= (A >> 1);
    A |= (A >> 2);
    A |= (A >> 4);
    A |= (A >> 8);
    A |= (A >> 16);
    A |= (A >> 32);
    return A + 1;
}

Este es un método de 64 bits para proporcionar un tamaño de matriz. Cuando es necesario asignar espacio para una matriz de depósito, este método se utilizará para determinar el tamaño de la matriz. Este algoritmo puede cubrir desde el bit 1 más alto hasta todos los bits más bajos. Por ejemplo , A = 0b10000, (A >> 1) = 0b01000, AND bit a bit obtendrá A = 0b11000, en este momento (A >> 2) = 0b00110, AND bit a bit obtendrá A = 0b11110. Por analogía, el bit más alto de A será 1. Cubre los 2 bits altos, los 4 bits altos, los 8 bits altos y el bit más bajo. Finalmente, el número binario lleno con 1 sumará 1 para obtener 0b1000...(N ceros). En otras palabras, el tamaño de la matriz del depósito será 2^n.

La lógica de trabajo de RefcountMap
1. SideTablesObtener SideTable. El recuento de referencia del objeto con valores hash duplicados se almacena en el mismo SideTable.
2. SideTableUsando find()métodos y operadores [] sobrecargados, determine el depósito correspondiente al objeto a través de la dirección del objeto. El algoritmo de búsqueda final ejecutado es LookupBucketFor().
3. El algoritmo de búsqueda primero juzgará el número de depósitos. Si el número de depósitos es 0, volverá return falseal nivel anterior y llamará el método de inserción. Si el algoritmo de búsqueda Si se encuentra un depósito vacío o un depósito de lápida, lo mismo return falsevuelve al nivel anterior para llamar al algoritmo de inserción, pero el depósito encontrado se registrará primero. Si el depósito correspondiente al objeto es encontrado, solo se necesita su recuento de referencia + 1 o - 1. Si el recuento de referencia es 0 Si necesita destruir el objeto, keyestablezca el valor enTombstoneKey

value_type& FindAndConstruct(const KeyT &Key) {
    
    
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return *TheBucket;
    return *InsertIntoBucket(Key, ValueT(), TheBucket);
  }

4. El algoritmo de inserción primero verificará la cantidad disponible. Si la cantidad disponible de la tabla hash (el número de depósitos de lápidas + depósitos vacíos) es inferior a 1/4, entonces es necesario volver a abrir un espacio más grande para la tabla. Si hay pocos depósitos vacíos en la tabla, es inferior a 1/8 (lo que indica que hay demasiados depósitos de lápidas), debe limpiar los lápidas en la tabla. En los dos casos anteriores, el algoritmo de búsqueda hash Es difícil encontrar la ubicación correcta e incluso puede generar un bucle infinito, por lo que la tabla debe procesarse primero. Luego, la tabla reasignará las posiciones de todos los depósitos y luego volverá a buscar las posiciones disponibles del objeto actual e insertará Si las dos situaciones anteriores no ocurren, el recuento de referencias del nuevo objeto se colocará directamente en el depósito proporcionado por la persona que llama.

Insertar descripción de la imagen aquí

La función de la lápida:

  1. Si el depósito con índice 2 se establece en un depósito vacío en lugar de un depósito de lápida después de que se destruye el objeto c, el recuento de referencias aumenta para el objeto e en este momento. Cuando se encuentra el depósito con índice 2 de acuerdo con el algoritmo hash, se insertará directamente. Es imposible aumentar el recuento de referencias para e que ya está en el depósito con el subíndice 4, pero en nuestro proceso normal, después de que se destruye el objeto c, el depósito con el subíndice 2 se establecerá como un depósito de lápida En este caso, cuando se aumenta el recuento de referencias para el objeto e Cuando se encuentra el depósito con el subíndice 2 según el algoritmo hash, se omitirá 2 y la búsqueda continuará hasta que se encuentre el depósito correspondiente al objeto e, o hasta que se encuentre un depósito vacío y se cree un nuevo depósito para almacenar el objeto e.
  2. Si se inicializa un nuevo objeto f en este momento, se encuentra el depósito con el subíndice 2 de acuerdo con el algoritmo hash y se encuentra una lápida en el depósito, se registrará el subíndice 2. A continuación, continúe con el algoritmo hash para encontrar la ubicación. , y encuentre Cuando se alcanza el depósito vacío, demuestra que no hay ningún objeto f en la tabla. En este momento, f usa el depósito de lápida registrado con el subíndice 2 en lugar del depósito vacío encontrado, y puede usar la posición liberada para garantizar que la parte anterior de la tabla hash está. Todos están en uso o esperando a ser usados.

El código fuente para encontrar el depósito correspondiente a un objeto es el siguiente:

bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    
    
    ...
    if (NumBuckets == 0) {
    
     //桶数是0
      FoundBucket = 0;
      return false; //返回 false 回上层调用添加函数
    }
    ...
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); //将哈希值与数组最大下标按位与
    unsigned ProbeAmt = 1; //哈希值重复的对象需要靠它来重新寻找位置
    while (1) {
    
    
      const BucketT *ThisBucket = BucketsPtr + BucketNo; //头指针 + 下标, 类似于数组取值
      //找到的桶中的 key 和对象地址相等, 则是找到
      if (KeyInfoT::isEqual(Val, ThisBucket->first)) {
    
    
        FoundBucket = ThisBucket;
        return true;
      }
      //找到的桶中的 key 是空桶占位符, 则表示可插入
      if (KeyInfoT::isEqual(ThisBucket->first, EmptyKey)) {
    
     
        if (FoundTombstone) ThisBucket = FoundTombstone; //如果曾遇到墓碑, 则使用墓碑的位置
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false; //找到空占位符, 则表明表中没有已经插入了该对象的桶
      }
      //如果找到了墓碑
      if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone)
        FoundTombstone = ThisBucket;  // 记录下墓碑
      //这里涉及到最初定义 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap, 传入的第三个参数 true
      //这个参数代表是否可以清除 0 值, 也就是说这个参数为 true 并且没有墓碑的时候, 会记录下找到的 value 为 0 的桶
      if (ZeroValuesArePurgeable  && 
          ThisBucket->second == 0  &&  !FoundTombstone) 
        FoundTombstone = ThisBucket;

      //用于计数的 ProbeAmt 如果大于了数组容量, 就会抛出异常
      if (ProbeAmt > NumBuckets) {
    
    
          _objc_fatal("...");
      }
      BucketNo += ProbeAmt++; //本次哈希计算得出的下表不符合, 则利用 ProbeAmt 寻找下一个下标
      BucketNo&= (NumBuckets-1); //得到新的数字和数组下标最大值按位与
    }
  }

Inserte el código en el depósito de recuento de referencias de un objeto de la siguiente manera:

BucketT *InsertIntoBucketImpl(const KeyT &Key, BucketT *TheBucket) {
    
    
    unsigned NewNumEntries = getNumEntries() + 1; //桶的使用量 +1
    unsigned NumBuckets = getNumBuckets(); //桶的总数
    if (NewNumEntries*4 >= NumBuckets*3) {
    
     //使用量超过 3/4
      this->grow(NumBuckets * 2); //数组大小 * 2做参数, grow 中会决定具体数值
      //grow 中会重新布置所有桶的位置, 所以将要插入的对象也要重新确定位置
      LookupBucketFor(Key, TheBucket);
      NumBuckets = getNumBuckets(); //获取最新的数组大小
    }
    //如果空桶数量少于 1/8, 哈希查找会很难定位到空桶的位置
    if (NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8) {
    
    
      //grow 以原大小重新开辟空间, 重新安排桶的位置并能清除墓碑
      this->grow(NumBuckets);
      LookupBucketFor(Key, TheBucket); //重新布局后将要插入的对象也要重新确定位置
    }
    assert(TheBucket);
    //找到的 BucketT 标记了 EmptyKey, 可以直接使用
    if (KeyInfoT::isEqual(TheBucket->first, getEmptyKey())) {
    
    
      incrementNumEntries(); //桶使用量 +1
    }
    else if (KeyInfoT::isEqual(TheBucket->first, getTombstoneKey())) {
    
     //如果找到的是墓碑
      incrementNumEntries(); //桶使用量 +1
      decrementNumTombstones(); //墓碑数量 -1
    }
    else if (ZeroValuesArePurgeable  &&  TheBucket->second == 0) {
    
     //找到的位置是 value 为 0 的位置
      TheBucket->second.~ValueT(); //测试中这句代码被直接跳过并没有执行, value 还是 0
    } else {
    
    
      // 其它情况, 并没有成员数量的变化(官方注释是 Updating an existing entry.)
    }
    return TheBucket;
  }

2. débil 部分——weak_table_t

weak_table_tEn SideTablela estructura, Hashla tabla que almacena el puntero de referencia débil del objeto weakes la estructura de datos central para la implementación de la función.
Primero, echemos un vistazo al weak_table_tcódigo fuente de la estructura:

struct weak_table_t {
    
    
    weak_entry_t *weak_entries;//连续地址空间的头指针,数组
    size_t    num_entries;//数组中已占用位置的个数
    uintptr_t mask;//数组下标最大值(即数组大小 -1)
    uintptr_t max_hash_displacement;//最大哈希偏移值
};

débil_table es una estructura de tabla hash. El valor hash se calcula en función de la dirección del objeto señalado por el puntero débil. Los objetos con el mismo valor hash buscan ubicaciones disponibles en forma de subíndice + 1. Es un típico cerrado algoritmo hash Máximo El valor de desplazamiento hash es el desplazamiento máximo entre el valor hash calculado y la posición de inserción real en todos los objetos, que se puede utilizar como el límite superior del bucle durante la búsqueda.

diagrama de estructura de tabla_débil:
Insertar descripción de la imagen aquí

2.1 Miembros de débil_entry_t

struct weak_entry_t {
    
    
    DisguisedPtr<objc_object> referent; //对象地址
    union {
    
      //这里又是一个联合体, 苹果设计的数据结构的确很棒
        struct {
    
    
            // 因为这里要存储的又是一个 weak 指针数组, 所以苹果继续选择采用哈希算法
            weak_referrer_t *referrers; //指向 referent 对象的 weak 指针数组
            uintptr_t        out_of_line_ness : 2; //这里标记是否超过内联边界, 下面会提到
            uintptr_t        num_refs : PTR_MINUS_2; //数组中已占用的大小
            uintptr_t        mask; //数组下标最大值(数组大小 - 1)
            uintptr_t        max_hash_displacement; //最大哈希偏移值
        };
        struct {
    
    
            //这是一个取名叫内联引用的数组
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT]; //宏定义的值是 4
        };
    };
    // weak_entry_t 的赋值操作,直接使用 memcpy 函数拷贝 other 内存里面的内容到 this 中,
    // 而不是用复制构造函数什么的形式实现,应该也是为了提高效率考虑的...
    weak_entry_t& operator=(const weak_entry_t& other) {
    
    
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    // 返回 true 表示使用 referrers 哈希数组 false 表示使用 inline_referrers 数组保存 weak_referrer_t
    bool out_of_line() {
    
    
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    // weak_entry_t 的构造函数
    
    // newReferent 是原始对象的指针,
    // newReferrer 则是指向 newReferent 的弱引用变量的指针。
    
    // 初始化列表 referent(newReferent) 会调用: DisguisedPtr(T* ptr) : value(disguise(ptr)) { } 构造函数,
    // 调用 disguise 函数把 newReferent 转化为一个整数赋值给 value。
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
    
    
        // 把 newReferrer 放在数组 0 位,也会调用 DisguisedPtr 构造函数,把 newReferrer 转化为整数保存
        inline_referrers[0] = newReferrer;
        // 循环把 inline_referrers 数组的剩余 3 位都置为 nil
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
    
    
            inline_referrers[i] = nil;
        }
    }
}

A través de la dirección del objeto, podemos weak_table_tencontrar la correspondiente en weak_entry_t, weak_entry_tque almacena todos weaklos punteros que apuntan a este objeto.

Apple weak_entry_tusa otra unión en . La primera estructura out_of_line_nessocupa 2 bits y num_refs62 bits en un entorno de 64 bits, por lo que en realidad ambas estructuras tienen 32 bytes y comparten una dirección. Al apuntar a este objeto, si no hay más de 4 punteros débiles, use la matriz directamente inline_referrers, eliminando el paso de la operación hash. Si weakel número de punteros excede 4, debe usar la tabla hash en la primera estructura.

2.2 La lógica general de débil_table

  • En ARC, el compilador agregará automáticamente código para administrar el recuento de referencias. weakAl asignar un puntero, el compilador llamará storeWeakpara asignar el valor. Si weakel puntero apunta a un objeto, primero llamará al método para eliminar el puntero weak_unregister_no_lock()de la tabla original. . weak, y luego llame para insertar este puntero weak_register_no_lock()en la tabla correspondienteweak
  • Al buscar, primero use la dirección del objeto señalado para calcular el valor hash SideTables(), busque el correspondiente SideTabley luego use esta dirección de objeto SideTablepara weak_tableencontrar el correspondiente weak_entry_t. La operación final es esta weak_entry_t:
    si weakel puntero de este objeto no excede 4. La matriz se operará directamente inline_referrers; de ​​lo contrario, referrersse solicitará memoria para la matriz y se utilizará el algoritmo hash para administrar la tabla.
  • Cuando se elimina el puntero anterior weak, la dirección del objeto originalmente señalado se utilizará para encontrar el correspondiente weak_entry_ty el puntero se eliminará de él weak. Si weakla matriz de punteros está vacía después de la eliminación, esta matriz de punteros se destruirá. la weak_entry_tposición original estará vacía y se eliminará la referencia isadel punteroweak
  • Al agregar un nuevo puntero débil, si se encuentra el correspondiente , el puntero weak_entry_tse insertará en la matriz, si no se encuentra, se creará e insertará una matriz configurada.weakreferrersweak_entry_tweak_table_t

3. Métodos de implementación importantes de débiles.

3.1 objc_initFunción débil

objc_initWeaknewObjLa función principal de la función es inicializar un __weakpuntero de objeto modificado en función del objeto entrante , manejar la situación de objetos no válidos y realizar algunas operaciones de optimización del rendimiento.

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

Luego, echemos un vistazo a objc_initWeak()lo que representan los dos parámetros pasados:

  • location: __weakLa dirección del puntero almacena la dirección del puntero, de modo que el objeto al que apunta se pueda establecer en cero al final.
  • newObj:El objeto al que se hace referencia. Eso está en el ejemplo p.

La función de esta función es la siguiente:

  • Primero, verifica si el objeto pasado newObjes válido y si newObjes un objeto no válido (es decir nil), luego establece el puntero al que apunta locationy lo devuelve directamente .__weaknilnil
  • Si newObjes un objeto válido, llamará storeWeaka la función para realizar la operación de inicialización de referencia débil real.
  • La función storeWeak es una función interna de bajo nivel que newObjalmacena el objeto en locationla dirección de memoria señalada, establece el bit de bandera y realiza algunas operaciones de optimización del rendimiento.

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.

3.2 objc_storeWeak()

storeWeakEl siguiente código es una implementación de plantilla de una función utilizada para implementar referencias débiles en el tiempo de ejecución de Objective-C . Esta función se utiliza principalmente para actualizar el puntero del puntero de referencia débil y manejar conflictos de competencia en situaciones de múltiples subprocesos.

// 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:
    if (HaveOld) {
    
    
    	// 更改指针,获得以 oldObj 为索引所存储的值地址
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
    
    
        oldTable = nil;
    }
    if (HaveNew) {
    
    
    	// 更改新值指针,获得以 newObj 为索引所存储的值地址
        newTable = &SideTables()[newObj];
    } else {
    
    
        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();
        // 判断 isa 非空且已经初始化
        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 指针进行标记
            previouslyInitializedClass = cls;
			// 重新尝试
            goto retry;
        }
    }
    // ② 清除旧值
    if (HaveOld) {
    
    
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // ③ 分配新值
    if (HaveNew) {
    
    
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, 
                                                      (id)newObj, location, 
                                                      CrashIfDeallocating);
        // 如果弱引用被释放 weak_register_no_lock 方法返回 nil 
        // 在引用计数表中设置弱引用标记位
        if (newObj  &&  !newObj->isTaggedPointer()) {
    
    
        	// 弱引用位初始化操作
			// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
            newObj->setWeaklyReferenced_nolock();
        }
        // 之前不要设置 location 对象,这里需要更改指针指向
        *location = (id)newObj;
    }
    else {
    
    
        // 没有新值,则无需更改
    }
    SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
    return (id)newObj;
}

Te explicaré paso a paso los principales pasos y funciones de esta función:

  1. Primero, la función declara algunas variables, incluyendo previouslyInitializedClassmarcar la clase inicializada, oldObjalmacenar la referencia del objeto antiguo oldTabley newTablerepresentar el objeto antiguo y el objeto nuevo SideTable(una estructura de datos que almacena información de referencia débil).
  2. retrySe implementa un mecanismo de reintento a través de la etiqueta para manejar conflictos de subprocesos.
  3. Luego, la función obtiene el objeto antiguo y el objeto nuevo según los parámetros de plantilla pasados ​​y HaveOldbloquea estos dos para evitar la competencia de subprocesos múltiples.HaveNewSideTableSideTable::lockTwoHaveOld, HaveNew>SideTable
  4. Evite conflictos de subprocesos: después del bloqueo, la función verificará locationsi es oldObjconsistente con. Si es inconsistente, significa que el actual locationha sido procesado oldObjpero ha sido modificado por otros subprocesos. Para evitar conflictos, debe volver a ejecutar retryel código en la etiqueta y volver a adquirir el objeto antiguo.
  5. Evite puntos muertos entre referencias débiles: la función verifica si el nuevo objeto existe y si el nuevo objeto no nil. Si es así, obtenga isael puntero al nuevo objeto y verifique isasi se ha inicializado. Si no está inicializado, inicialícelo primero y configúrelo previouslyInitializedClasscomo una etiqueta, y luego vuelva a ejecutar retryel código en la etiqueta para evitar que otros subprocesos compitan.
  6. A continuación, la función borrará el valor anterior (cancelará la referencia débil del objeto antiguo) según el parámetro de plantilla HaveOldy HaveNewasignará un nuevo valor (agregará una referencia débil al nuevo objeto) según el parámetro de plantilla.
  7. Si el nuevo objeto se registra con éxito y se asigna una referencia débil, el bit de referencia débil se inicializa y el locationpuntero del objeto puntiagudo se actualiza al puntero del nuevo objeto.
  8. Finalmente, la función desbloquea SideTable::unlockTwoHaveOld, HaveNew>los dos objetos bloqueados SideTabley devuelve el puntero del nuevo objeto.

3.3 registro_débil_no_lock

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 {
    
    
        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;
}

Este código es la implementación de una función utilizada en el tiempo de ejecución de Objective-C para registrar una referencia débil en la tabla de referencia débil weak_register_no_lock. Esta función se utiliza para weak_tableagregar una relación de referencia débil y registrar el puntero de referencia débil de un objeto.

Ahora, explicaré paso a paso los principales pasos y efectos del código:

  1. Primero, la función convierte los punteros de tipo entrantes referent_idy referrer_iden objc_objectrespectivamente y los asigna a referentlas referrervariables y respectivamente.
  2. Luego, la función verifica referentsi es nulo o usa TaggedPointerun recuento ( Tagged Pointerun mecanismo de optimización utilizado para almacenar el puntero del objeto directamente en el puntero en algunos casos sin asignación de memoria adicional, y no hay necesidad de lidiar con referencias débiles aquí).
  3. A continuación, la función comprueba si el objeto al que se hace referencia está referentdisponible, es decir, que no está en proceso de destrucción y admite referencias débiles. Cabe señalar aquí que en Objective-C, algunos objetos pueden allowsWeakReferencedecidir si admiten referencias débiles anulando métodos. Por lo tanto, para objetos con métodos de recuento de referencias personalizados, la función llamará allowsWeakReferencea un método para comprobar si el objeto admite referencias débiles.
  4. Si el objeto al que se hace referencia referentestá en proceso de destrucción ( deallocating为true), según crashIfDeallocatingel valor del parámetro, la función decidirá si devolverá nulo o generará una excepción. Si crashIfDeallocatinges true, _objc_fatalse generará una excepción; de lo contrario, se devolverá nil.
  5. Si el objeto al que se hace referencia está referentdisponible y admite referencias débiles, continúe con los siguientes pasos.
  6. La función weak_tablecomprobará si referentla entrada de referencia débil correspondiente ya existe en weak_entry. Si lo encuentra, referreragréguelo a weak_entryla matriz de referencia en el archivo . Si no se encuentra, cree uno nuevo weak_entrye insértelo en weak_table, luego agréguelo a la matriz de referencia en referrereste nuevo .weak_entry
  7. Finalmente, la función devuelve el puntero referent_idal objeto referenciado pasado referent.

3.4 entrada_débil_para_referente

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;
        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];
}

Esta es una función llamada que se utiliza para encontrar la entrada correspondiente weak_entry_for_referentpara un objeto referenciado determinado en la tabla de referencia débil .referentweak_entry

3.5 append_referrer

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    
    
    if (! entry->out_of_line()) {
    
     // 如果weak_entry 尚未使用动态数组,走这里
        // 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()); // 断言: 此时一定使用的动态数组

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
    
     // 如果动态数组中元素个数大于或等于数组位置总空间的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 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
    }
    if (hash_displacement > entry->max_hash_displacement) {
    
     // 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
        entry->max_hash_displacement = hash_displacement;
    }
    // 将ref存入hash数组,同时,更新元素个数num_refs
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

Este código primero determina si se usa una matriz de longitud fija o una matriz dinámica. Si se usa una matriz de longitud fija, simplemente agregue la dirección del puntero débil a la matriz. Si la matriz de longitud fija se ha agotado, debe agregar los elementos en la matriz de longitud fija.Transfiera a la matriz dinámica.

A continuación, echemos un vistazo al método de eliminación del puntero débil, que se weak_entryllama cuando es necesario borrarlo: weak_unregister_no_lock, la antigua dirección del puntero débil se elimina en el método.

3.6 débil_unregister_no_lock

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    
    
	//对象的地址
    objc_object *referent = (objc_object *)referent_id;
    //weak指针地址
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    
     // 查找到referent所对应的weak_entry_t
        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;
                }
            }
        }

        if (empty) {
    
     // 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }
    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

Proceso aproximado:

Primero, encontrará el débil_entry_t correspondiente al referente en la tabla débil. Después
de eliminar el referente en débil_entry_t
, juzgará si todavía hay elementos en débil_entry_t en este momento (vacío==verdadero?).
Si débil_entry_t no tiene elementos en este momento tiempo, entonces Weak_entry_t necesita ser eliminado de débil_table

4. comprensión

4.1 rootDealloc

Cuando el recuento de referencias del objeto es 0, la capa inferior llamará al método _objc_rootDealloc para liberar el objeto, y se llamará al método rootDealloc en el método _objc_rootDealloc. La siguiente es la implementación del código del método rootDealloc:

xinline void
objc_object::rootDealloc()
{
    
    
    if (isTaggedPointer()) return;  // fixme necessary?

    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((id)this);
    }
}

Proceso aproximado:

  • Primero determine si el objeto es Tagged Pointery, de ser así, devuélvalo directamente.
  • Si el objeto adopta un isamétodo de conteo optimizado y si débil no hace referencia al objeto !isa.weakly_referenced, no tiene objetos asociados !isa.has_assoc, no tiene un método destructor de C++ personalizado !isa.has_cxx_dtory no se usa SideTablepara el conteo de referencias, !isa.has_sidetable_rcse lanzará rápidamente.
  • Si no se pueden cumplir las condiciones en 2, object_disposese llamará al método.

4.2 objeto_disponer

void *objc_destructInstance(id obj)
{
    
    
    if (obj) {
    
    
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_associations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

Si hay un método destructor de C++ personalizado, se llama al destructor de C++. Si hay un objeto asociado, elimine el objeto asociado y elimínelo del Association Managermapa. Llame clearDeallocatingal método para borrar las referencias relevantes al objeto.

4.3 clara Desasignación

inline void 
objc_object::clearDeallocating()
{
    
    
    if (slowpath(!isa.nonpointer)) {
    
    
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    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());
}

clearDeallocatingHay dos ramas en. Primero, determine si el objeto utiliza un recuento de referencias isa optimizado. De lo contrario, debe llamar a un método para limpiar los datos del recuento de referencias sidetable_clearDeallocatingalmacenados en el objeto . SideTableSi el objeto utiliza un recuento de referencia isa optimizado, determine si se utiliza SideTableun recuento de referencia auxiliar (isa.has_sidetable_rc)o una referencia débil (isa.weakly_referenced). Si se cumple una de estas dos situaciones, llame clearDeallocating_slowal método.

4.4 sidetable_clearDeallocating

void 
objc_object::sidetable_clearDeallocating()
{
    
    
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    //清除所有弱表项
	//清除额外的保留计数和释放位
	//(如果额外保留计数==0,则修复警告或中止)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
    
    
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
    
    
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

4.5 clearDeallocating_slow

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    
    
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

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

4.6 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;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); // 找到referent在weak_table中对应的weak_entry_t
    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) {
    
    
        objc_object **referrer = referrers[i]; // 取出每个weak ptr的地址
        if (referrer) {
    
    
            if (*referrer == referent) {
    
     // 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
                *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();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}

Finalmente, veamos cómo destruir los punteros débiles:

void
objc_destroyWeak(id *location)
{
    
    
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

Después de llamar al storeWeakmétodo aquí, dado que no apunta a un nuevo objeto, si nuestro weakpuntero originalmente apuntaba a un objeto, irá a: weak_unregister_no_lockpara eliminar la antigua dirección del puntero débil y establecerla en nula.

Resumir

  • El principio de débil es que la capa inferior mantiene una weak_table_ttabla hash estructural, la clave es la dirección del objeto apuntado y el valor es la matriz de direcciones del puntero débil.
  • La función de la palabra clave débil es una referencia débil. El contador del objeto referenciado no aumentará en 1 y se establecerá automáticamente en cero cuando se libere el objeto referenciado.
  • Cuando se libera el objeto, la clearDeallocatingfunción de llamada obtiene la matriz de todas las direcciones de puntero débil según la dirección del objeto, luego atraviesa la matriz y establece los datos en ella en cero, finalmente elimina la entrada de la tabla débil y finalmente limpia el registros del objeto.
  • El artículo presenta tres estructuras SideTable: , weak_table_t, weak_entry_tetc.

Supongo que te gusta

Origin blog.csdn.net/m0_63852285/article/details/131858578
Recomendado
Clasificación