Faiblesse de la gestion de la mémoire ios

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 : image.pngle 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 : image.pnggrâ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); image.png

  1. <DontHaveOld, DoHaveNew, DoCrashIfDeallocation> représentent les paramètres de modèle.
  2. HaveOld indique si le pointeur faible pointe vers une référence faible
  3. HaveNew indique si le pointeur faible doit uniquement pointer vers une nouvelle référence faible
  4. 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_lockpour supprimer l'adresse du pointeur faible. Si le pointeur faible pointe vers une nouvelle référence faible, il appellera weak_register_no_lockpour ajouter l'adresse du pointeur faible à la table de référence faible de l'objet, en setWeaklyReferenced_nolockmettant 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.

image.png 然后我们在看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关系图: image.png

三、weak引用计数问题

重新回到例子1的问题,但是__weak修饰的weakP为什么RetainCount是2呢?

我们开始断点调试,看看CFGetRetainCount弱引用和强引用有什么区别。

查看强引用汇编:

image.png 查看弱引用汇编: image.png 看到weakPCFGetRetainCount前会执行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
}
复制代码

继续断点控制台打印: image.png 我们在看一个情况:

        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));

        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(weakP)));

        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(p)));
复制代码

Regardez le résultat d'impression : image.pngpourquoi le nombre de références de obj +1 après avoir traversé la objc_loadWeakRetainedfonction 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_loadWeakRetainedré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

  1. storeWeak: __weak est implémenté en bas storeWeak函数, 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_lockpour supprimer l'adresse du pointeur faible.
    • Si le pointeur faible pointe vers une nouvelle référence faible, il appellera weak_register_no_lockpour ajouter l'adresse du pointeur faible à la table de référence faible de l'objet
  2. weak原理: Il s'agit de trouver la table de table faible pertinente à travers la sideTable de l'objet.Les weak_unregister_no_lockdeux weak_register_no_lockfonctions 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, la weak_entry_for_referentmé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é, la append_referrermé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 la weak_entry_for_referentméthode pour trouver l'objet faiblement référencé correspondant weak_entry_tdans la table_faible, et supprimez l'adresse du pointeur faible dans l'entrée_faible_t.
  3. weak引用计数问题: Il y en a un dans la fonction CFGetRetainCount((__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_loadWeakRetainedrootTryRetain()->rootRetain()

Guess you like

Origin juejin.im/post/7176168914060050492