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
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 ZeroValuesArePurgeable
valor predeterminado es false
, pero RefcountMap
especifique 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. Buckets
Los punteros administran un espacio de memoria continuo, es decir, una matriz. Los miembros de la matriz son BucketT
objetos de tipo. BucketT
Aquí 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 key
es , EmptyKey
es 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::pair
similar al swift
tipo 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 bit
reservadosbit
BucketT
se define de la siguiente manera:
typedef std::pair<KeyT, ValueT> BucketT;
3. NumEntries
Registre la cantidad de depósitos no vacíos usados en la matriz.
4. NumTombstones
, Tombstone
traducido 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 Tombstone
número NumTombstones
de lápidas en la matriz. El papel de las lápidas será introducido más tarde.
5. NumBuckets
El 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. SideTables
Obtener SideTable
. El recuento de referencia del objeto con valores hash duplicados se almacena en el mismo SideTable
.
2. SideTable
Usando 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 false
al 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 false
vuelve 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, key
establezca 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.
La función de la lápida:
- 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.
- 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_t
En SideTable
la estructura, Hash
la tabla que almacena el puntero de referencia débil del objeto weak
es la estructura de datos central para la implementación de la función.
Primero, echemos un vistazo al weak_table_t
có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:
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_t
encontrar la correspondiente en weak_entry_t
, weak_entry_t
que almacena todos weak
los punteros que apuntan a este objeto.
Apple weak_entry_t
usa otra unión en . La primera estructura out_of_line_ness
ocupa 2 bits y num_refs
62 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 weak
el 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.
weak
Al asignar un puntero, el compilador llamarástoreWeak
para asignar el valor. Siweak
el puntero apunta a un objeto, primero llamará al método para eliminar el punteroweak_unregister_no_lock()
de la tabla original. .weak
, y luego llame para insertar este punteroweak_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 correspondienteSideTable
y luego use esta dirección de objetoSideTable
paraweak_table
encontrar el correspondienteweak_entry_t
. La operación final es estaweak_entry_t
:
siweak
el puntero de este objeto no excede 4. La matriz se operará directamenteinline_referrers
; de lo contrario,referrers
se 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 correspondienteweak_entry_t
y el puntero se eliminará de élweak
. Siweak
la matriz de punteros está vacía después de la eliminación, esta matriz de punteros se destruirá. laweak_entry_t
posición original estará vacía y se eliminará la referenciaisa
del punteroweak
- Al agregar un nuevo puntero débil, si se encuentra el correspondiente , el puntero
weak_entry_t
se insertará en la matriz, si no se encuentra, se creará e insertará una matriz configurada.weak
referrers
weak_entry_t
weak_table_t
3. Métodos de implementación importantes de débiles.
3.1 objc_initFunción débil
objc_initWeak
newObj
La función principal de la función es inicializar un __weak
puntero 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
:__weak
La 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 ejemplop
.
La función de esta función es la siguiente:
- Primero, verifica si el objeto pasado
newObj
es válido y sinewObj
es un objeto no válido (es decirnil
), luego establece el puntero al que apuntalocation
y lo devuelve directamente .__weak
nil
nil
- Si
newObj
es un objeto válido, llamarástoreWeak
a 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
newObj
almacena el objeto enlocation
la dirección de memoria señalada, establece el bit de bandera y realiza algunas operaciones de optimización del rendimiento.
objc_initWeak
La función tiene un requisito previo: object
debe ser un __weak
puntero válido que no haya sido registrado como objeto. Y value
puede ser nil
o apuntar a un objeto válido.
3.2 objc_storeWeak()
storeWeak
El 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:
- Primero, la función declara algunas variables, incluyendo
previouslyInitializedClass
marcar la clase inicializada,oldObj
almacenar la referencia del objeto antiguooldTable
ynewTable
representar el objeto antiguo y el objeto nuevoSideTable
(una estructura de datos que almacena información de referencia débil). retry
Se implementa un mecanismo de reintento a través de la etiqueta para manejar conflictos de subprocesos.- Luego, la función obtiene el objeto antiguo y el objeto nuevo según los parámetros de plantilla pasados y
HaveOld
bloquea estos dos para evitar la competencia de subprocesos múltiples.HaveNew
SideTable
SideTable::lockTwoHaveOld, HaveNew>
SideTable
- Evite conflictos de subprocesos: después del bloqueo, la función verificará
location
si esoldObj
consistente con. Si es inconsistente, significa que el actuallocation
ha sido procesadooldObj
pero ha sido modificado por otros subprocesos. Para evitar conflictos, debe volver a ejecutarretry
el código en la etiqueta y volver a adquirir el objeto antiguo. - 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í, obtengaisa
el puntero al nuevo objeto y verifiqueisa
si se ha inicializado. Si no está inicializado, inicialícelo primero y configúrelopreviouslyInitializedClass
como una etiqueta, y luego vuelva a ejecutarretry
el código en la etiqueta para evitar que otros subprocesos compitan. - 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
HaveOld
yHaveNew
asignará un nuevo valor (agregará una referencia débil al nuevo objeto) según el parámetro de plantilla. - 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
location
puntero del objeto puntiagudo se actualiza al puntero del nuevo objeto. - Finalmente, la función desbloquea
SideTable::unlockTwoHaveOld, HaveNew>
los dos objetos bloqueadosSideTable
y 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_table
agregar 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:
- Primero, la función convierte los punteros de tipo entrantes
referent_id
yreferrer_id
enobjc_object
respectivamente y los asigna areferent
lasreferrer
variables y respectivamente. - Luego, la función verifica
referent
si es nulo o usaTaggedPointer
un recuento (Tagged Pointer
un 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í). - A continuación, la función comprueba si el objeto al que se hace referencia está
referent
disponible, 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 puedenallowsWeakReference
decidir 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áallowsWeakReference
a un método para comprobar si el objeto admite referencias débiles. - Si el objeto al que se hace referencia
referent
está en proceso de destrucción (deallocating为true
), segúncrashIfDeallocating
el valor del parámetro, la función decidirá si devolverá nulo o generará una excepción. SicrashIfDeallocating
estrue
,_objc_fatal
se generará una excepción; de lo contrario, se devolverá nil. - Si el objeto al que se hace referencia está
referent
disponible y admite referencias débiles, continúe con los siguientes pasos. - La función
weak_table
comprobará sireferent
la entrada de referencia débil correspondiente ya existe enweak_entry
. Si lo encuentra,referrer
agréguelo aweak_entry
la matriz de referencia en el archivo . Si no se encuentra, cree uno nuevoweak_entry
e insértelo enweak_table
, luego agréguelo a la matriz de referencia enreferrer
este nuevo .weak_entry
- Finalmente, la función devuelve el puntero
referent_id
al objeto referenciado pasadoreferent
.
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_referent
para un objeto referenciado determinado en la tabla de referencia débil .referent
weak_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_entry
llama 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 Pointer
y, de ser así, devuélvalo directamente. - Si el objeto adopta un
isa
mé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_dtor
y no se usaSideTable
para el conteo de referencias,!isa.has_sidetable_rc
se lanzará rápidamente. - Si no se pueden cumplir las condiciones en 2,
object_dispose
se 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 Manager
mapa. Llame clearDeallocating
al 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());
}
clearDeallocating
Hay 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_clearDeallocating
almacenados en el objeto . SideTable
Si el objeto utiliza un recuento de referencia isa optimizado, determine si se utiliza SideTable
un 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_slow
al 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 storeWeak
método aquí, dado que no apunta a un nuevo objeto, si nuestro weak
puntero originalmente apuntaba a un objeto, irá a: weak_unregister_no_lock
para 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_t
tabla 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
clearDeallocating
funció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_t
etc.