¡Comienza el viaje de crecimiento de los Nuggets! Este es el quinto día de mi participación en el "Desafío de actualización de diciembre del nuevo plan diario de Nuggets", haga clic para ver los detalles del evento
1. tiendaProceso débil
Veamos primero un código de ejemplo:
@autoreleasepool {
NYPerson *p = [NYPerson new];
__weak typeof(p) weakP = p;
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));
}
复制代码
Ejecute para ver los resultados de la impresión: El nuevo objeto RetainCount es 1, lo cual es fácil de entender.但是__weak修饰的weakP为什么RetainCount是2呢?
Entonces, ¿qué hace exactamente __weak debajo del capó?
Depuremos a través del punto de interrupción e ingresemos el código fuente para ver: a través de la impresión del punto de interrupción, sabemos que la ubicación en objc_initWeak(id *ubicación, id newObj) representa la dirección del puntero débilP, y newObj representa el objeto p NYPerson.
Luego vemos el método de núcleo real:storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
- <DontHaveOld, DoHaveNew, DoCrashIfDeallocating> representan parámetros de plantilla.
- HaveOld representa si el puntero débil apunta a una referencia débil
- HaveNew representa si el puntero débil solo necesita apuntar a una nueva referencia débil
- CrashIfDeallocating representa si el objeto débilmente referenciado se está destruyendo y si se está destruyendo, se producirá un error.
Ver el código central de storeWeak:
static id
storeWeak(id *location, objc_object *newObj)
{
//...........................省略.............................//
//获取两张表
SideTable *oldTable;//oldTable
SideTable *newTable;//newTable
retry:
if (haveOld) {
oldObj = *location;//拿到old被弱引用的对象
oldTable = &SideTables()[oldObj];//在通过这个对象获取oldTable-SideTable表
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];//在通过newObj获取newTable-SideTable表
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);//设置加锁
if (haveOld && *location != oldObj) {//haveOld 存在 并且 *location指针指向的老对象 和 oldObj不相同
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); //解锁
goto retry;//回到retry 再次判断
}
if (haveNew && newObj) {//新的弱引用指向 和 新对象
Class cls = newObj->getIsa();//拿到isa指向的元类
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) //判断类没有初始化
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//解锁
class_initialize(cls, (id)newObj);//初始化 +1
//...........................省略.............................//
goto retry;//重新retry
}
}
if (haveOld) {//如果曾指向老的对象
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);//移除在老的weak_table的数据
}
if (haveNew) {//如果有一个新引用
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
//添加新的引用和对象到weak_table表中
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!_objc_isTaggedPointerOrNil(newObj)) {//判断是否是TaggedPointer
newObj->setWeaklyReferenced_nolock();//设置newObj的weakly_referenced = true;是否被弱引用
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;//赋值到weakP指针指向的地址
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);//解锁
//...........................省略.............................//
return (id)newObj;
}
复制代码
resumen:
__weak se ejecuta en la parte inferior storeWeak函数
, su función es obtener de acuerdo con la ubicación y los parámetros newObj, oldTable y newTable y luego juzgar, si el puntero débil apunta a una referencia débil antes, llamará weak_unregister_no_lock
para eliminar la dirección del puntero débil. Si el puntero débil apunta a una nueva referencia débil, llamará weak_register_no_lock
para agregar la dirección del puntero débil a la tabla de referencia débil del objeto, setWeaklyReferenced_nolock
estableciéndola newisa.weakly_referenced
en verdadero;
Dos, principio débil
El artículo anterior ya ha entendido la estructura y función de débil_tabla_t .
Se están haciendo algunas adiciones aquí: weak_entry_t *weak_entries;
es una matriz hash que almacena información sobre objetos referenciados débilmente.
然后我们在看weak_register_no_lock
函数:
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
//referent -- 被弱引用的对象
//referrer -- weak指针的地址
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
//判断是否TaggedPointer 是返回referent_id
if (_objc_isTaggedPointerOrNil(referent)) return referent_id;
// 确保弱引用对象是可用的。
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
//...........................省略.............................//
}
weak_entry_t *entry;
//被弱引用的referent里面的weak_table中找到weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);// 往weak_entry_t里插入referrer -- weak指针的地址
}
else {
//没找到weak_entry_t就新建一张
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);//在把new_entry插入到weak_table中
}
return referent_id;
}
复制代码
weak_register_no_lock
添加弱引用函数流程:
- 如果被弱引用的对象为nil或这是一个
TaggedPointer
,直接返回,不做任何操作。 - 如果被弱引用的对象正在析构,则抛出异常。
- 如果被弱引用的对象不能被weak引用,直接返回nil。
- 如果对象没有再析构并且可以被weak引用,则调用
weak_entry_for_referent
方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry_t,如果能够找到则调用append_referrer
方法向其中插入weak指针地址。否则新建一个weak_entry_t。
我们在看weak_unregister_no_lock
函数:
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
//referent -- 被弱引用的对象
//referrer -- weak指针的地址
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
// 被弱引用的对象 ,不存在返回
if (!referent) return;
// 被弱引用的referent里面的weak_table中找到weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);//从weak_entry_t 中移除weak指针的地址
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 4张表中都不存在referrer 指针的地址,并且entry 中已经weak指针已被移除
if (empty) {
weak_entry_remove(weak_table, entry);//移除weak_table中的weak_entry_t
}
}
}
复制代码
weak_unregister_no_lock
移除弱引用函数流程:
referent
被弱引用的对象 ,不存在直接返回。- 通过
weak_entry_for_referent
方法在weak_table中找出被弱引用对象对应的weak_entry_t
。 - 在weak_entry_t中移除weak指针的地址。
- 移除元素后,判断此时
weak_entry_t
中是否还有元素,如果此时weak_entry_t
已经没有元素了,则需要将weak_entry_t
从weak_table中移除。
整理了一个对象sidetable,weak关系图:
三、weak引用计数问题
重新回到例子1的问题,但是__weak修饰的weakP为什么RetainCount是2呢?
我们开始断点调试,看看CFGetRetainCount弱引用和强引用有什么区别。
查看强引用汇编:
查看弱引用汇编: 看到weakP
在CFGetRetainCount
前会执行objc_loadWeakRetained
这个函数。
然后我们搜索objc_loadWeakRetained
这个函数进入源码:
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;//获取弱引指针指向的对象
if (_objc_isTaggedPointerOrNil(obj)) return obj;//如果是TaggedPointer直接返回
table = &SideTables()[obj];//获取对象的SideTable
table->lock();//加锁
if (*location != obj) {//指针指指向的对象和obj不相等
table->unlock();//解锁
goto retry;
}
result = obj;
cls = obj->ISA();//得到对象的ISA
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) {//rootTryRetain->rootRetain 这里加1了
result = nil;
}
}
else {
//...........................省略.............................//
}
table->unlock();
return result;//局部变量 在arc中会-1
}
复制代码
继续断点控制台打印: 我们在看一个情况:
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
复制代码
Mire el resultado de la impresión: ¿Por qué el recuento de referencias de obj es +1 después de pasar por la objc_loadWeakRetained
función y encontrarlo obj->rootTryRetain()
en el código fuente, pero el recuento de referencias de la fuente p sigue contando 1?
Debido a que el objc_loadWeakRetained
resultado en la función es una variable local en arco, se decrementará en 1 después de la ejecución, por lo tanto, no afectará el recuento de referencia del objeto p externo.
Resumir
storeWeak
: __weak se implementa en la parte inferiorstoreWeak函数
, su función es obtener de acuerdo con la ubicación y los parámetros newObj, y luego juzgar oldTable y newTable (ambos son tipos de sideTable).- Si el puntero débil apuntaba anteriormente a una referencia débil, se llamará
weak_unregister_no_lock
para eliminar la dirección del puntero débil. - Si el puntero débil apunta a una nueva referencia débil, llamará
weak_register_no_lock
para agregar la dirección del puntero débil a la tabla de referencia débil del objeto .
- Si el puntero débil apuntaba anteriormente a una referencia débil, se llamará
weak原理
: Es encontrar la tabla débil relevante a través de la tabla lateral del objeto. Lasweak_unregister_no_lock
dosweak_register_no_lock
funciones agregan y eliminan la dirección del puntero débil.weak_unregister_no_lock
: si el objeto no se vuelve a destruir y puede ser referenciado por débil, elweak_entry_for_referent
método de llamada encuentra la entrada débil correspondiente de la tabla de referencia débil de acuerdo con la dirección del objeto de referencia débil y, si se puede encontrar, elappend_referrer
método de llamada inserta el dirección del puntero débil en él. De lo contrario, cree una nueva entrada débil_t.weak_register_no_lock
: utilice elweak_entry_for_referent
método para encontrar el objeto de referencia débil correspondienteweak_entry_t
en la tabla débil y elimine la dirección del puntero débil en la entrada débil.
weak引用计数问题
: Hay uno en la funciónCFGetRetainCount((__bridge CFTypeRef)(weakP))
ejecutada antes de imprimir , y luego el conteo de referencia es +1. Y no afecta el recuento de referencias del objeto p externo.objc_loadWeakRetained
rootTryRetain()->rootRetain()