Commencez le voyage de croissance des pépites ! C'est le 5ème jour de ma participation au "Nuggets Daily New Plan·December Update Challenge", cliquez pour voir les détails de l'événement
1. processus storeWeak
Regardons d'abord un exemple de code :
@autoreleasepool {
NYPerson *p = [NYPerson new];
__weak typeof(p) weakP = p;
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));
}
复制代码
Exécutez pour afficher les résultats d'impression : le nouvel objet RetainCount est 1, ce qui est facile à comprendre.但是__weak修饰的weakP为什么RetainCount是2呢?
Alors que fait exactement __weak sous le capot ?
Déboguons à travers le point d'arrêt et entrons dans le code source pour voir : grâce à l'impression du point d'arrêt, nous savons que l'emplacement dans objc_initWeak(id *location, id newObj) représente l'adresse du pointeur faibleP, et newObj représente l'objet p NYPerson.
Ensuite, nous voyons la vraie méthode de base:storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
- <DontHaveOld, DoHaveNew, DoCrashIfDeallocation> représentent les paramètres de modèle.
- HaveOld indique si le pointeur faible pointe vers une référence faible
- HaveNew indique si le pointeur faible doit uniquement pointer vers une nouvelle référence faible
- CrashIfDeallocation représente si l'objet faiblement référencé est détruit, et s'il est détruit, une erreur se produira.
Voir le code principal 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;
}
复制代码
résumé:
__weak s'exécute en bas storeWeak函数
, sa fonction est d'obtenir selon les paramètres location et newObj, oldTable et newTable puis juge, si le pointeur faible pointe sur une référence faible avant, il appellera weak_unregister_no_lock
pour supprimer l'adresse du pointeur faible. Si le pointeur faible pointe vers une nouvelle référence faible, il appellera weak_register_no_lock
pour ajouter l'adresse du pointeur faible à la table de référence faible de l'objet, en setWeaklyReferenced_nolock
mettant newisa.weakly_referenced
à vrai ;
Deux, principe faible
L'article précédent a déjà compris la structure et la fonction de low_table_t .
Quelques ajouts sont faits ici : weak_entry_t *weak_entries;
c'est un tableau de hachage qui stocke des informations sur les objets faiblement référencés.
然后我们在看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)));
复制代码
Regardez le résultat d'impression : pourquoi le nombre de références de obj +1 après avoir traversé la objc_loadWeakRetained
fonction et l'avoir trouvé obj->rootTryRetain()
dans le code source, mais le nombre de références de la source p compte toujours 1 ?
Étant donné que le objc_loadWeakRetained
résultat de la fonction est une variable locale dans arc, elle sera décrémentée de 1 après l'exécution. Par conséquent, cela n'affectera pas le nombre de références de l'objet p à l'extérieur.
Résumer
storeWeak
: __weak est implémenté en basstoreWeak函数
, sa fonction est d'obtenir en fonction des paramètres location et newObj, puis de juger oldTable et newTable (les deux sont de type sideTable).- Si le pointeur faible pointait précédemment sur une référence faible, il sera appelé
weak_unregister_no_lock
pour supprimer l'adresse du pointeur faible. - Si le pointeur faible pointe vers une nouvelle référence faible, il appellera
weak_register_no_lock
pour ajouter l'adresse du pointeur faible à la table de référence faible de l'objet
- Si le pointeur faible pointait précédemment sur une référence faible, il sera appelé
weak原理
: Il s'agit de trouver la table de table faible pertinente à travers la sideTable de l'objet.Lesweak_unregister_no_lock
deuxweak_register_no_lock
fonctions ajoutent et suppriment l'adresse du pointeur faible.weak_unregister_no_lock
: Si l'objet n'est pas re-détruit et peut être référencé par faible, laweak_entry_for_referent
méthode appelante trouve le faible_entry_t correspondant dans la table de référence faible en fonction de l'adresse de l'objet de référence faible, et s'il peut être trouvé, laappend_referrer
méthode appelante insère le adresse du pointeur faible dans celui-ci. Sinon, créez un nouveau faible_entry_t.weak_register_no_lock
: Utilisez laweak_entry_for_referent
méthode pour trouver l'objet faiblement référencé correspondantweak_entry_t
dans la table_faible, et supprimez l'adresse du pointeur faible dans l'entrée_faible_t.
weak引用计数问题
: Il y en a un dans la fonctionCFGetRetainCount((__bridge CFTypeRef)(weakP))
exécutée avant l'impression , puis le compteur de références est +1. Et cela n'affecte pas le nombre de références de l'objet p externe.objc_loadWeakRetained
rootTryRetain()->rootRetain()