iOS面试题(二十)内存管理--弱引用表

 

 

5.内存管理

  • 内存布局
  • 内存管理方案(更好的简述内存管理方案相关的问题,就要明白他们的数据结构)
  • 数据结构
  • ARC&MRC(什么是ARC,什么是MRC,他们的区别以及各自实现的机制、原理)
  • 引用计数机制(什么是引用计数机制?内存是怎样管理的?)
  • 弱引用表(我们声明weak的一个变量,为什么在内存释放的时候,weak指针会自动置为nil?弱引用变量内存是怎么管理的?)
  • 自动释放池(AutoReleasePool)的实现机制和原理是怎样的?
  • 循环引用

弱引用表

 

//使用__weak关键字修饰的obj1变量指向一个通过alloc分配的一个对象obj,此时有了__weak弱引用指针

{
   id __weak obj1 = obj;
}

               |  代码块经过编译之后变成下面
               |
{
   id obj1;
   objc_initWeak(&obj1,obj); //实际上是使用objc_initWeak函数,传递了两个参数(弱引用变量的地址,被修饰的对象)
}

下面看下上面发生了什么过程,objc_initWeak的调用栈:

  1. objc_initWeak函数会调用storeWeak函数,
  2. 然后调用weak_register_no_lock函数,
    weak指针被添加到弱引用表的具体实现是在weak_register_no_lock中实现的

源码分析:(一个weak变量是怎样被添加到弱引用表当中的?)

objc_initWeak函数分析:

/*
  通过编译器编译之后,产生的函数objc_initWeak
  两个参数-弱引用变量,被弱引用的对象
*/
id objc_initWeak(id *location, id newObj)
{
    //判空操作
    if (!newObj) {
        *location = nil;
        return nil;
    }
   
    /* storeWeak<> 使用了c++的一个模板,里面传递了三个变量:
       老对象,新对象,在dealloc中是否可以被crash。
       然后再传递两个实际的参数
    */
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

storeWeak函数分析:

storeWeak(id *location, objc_object *newObj)
{    
    /*
      在这里按有新对象,没有旧对象的场景来分析实际调用逻辑
      HaveOld = false   HaveNew = true
    */

    assert(HaveOld  ||  HaveNew);
    if (!HaveNew) assert(newObj == nil);

    //定义几个局部变量
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

 retry:
    if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        /*
        newObj:被week指针修饰的对象
        通过newObj的指针到SideTables这个哈希表当中,
        找到这个对象对应的SideTable,就是newTable
        */
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);

    if (HaveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
        goto retry;
    }

    if (HaveNew  &&  newObj) {
        //根据当前对象获取它的isa指针来找到它的类对象
        Class cls = newObj->getIsa();
        //对类对象进行一个是否已经初始化过的判断
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));


            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    if (HaveNew) {
        /*
         weak_register_no_lock参数:
             &newTable->weak_table:从对象所属的side_table取出weak_table(弱引用表的地址),
             (id)newObj:被弱引用指向的原对象,
             location:弱引用指针,
             CrashIfDeallocating:对象在废弃过程中的crash的标志位

        */
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
                                                      (id)newObj, location, 
                                                      CrashIfDeallocating);
        /*
          如果新对象是有值的,并且不是小对象的这种管理方式,
          就设置它为有弱引用的标志位。
        */
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        *location = (id)newObj;
    }

weak_register_no_lock分析:

/*
  weak_table_t *weak_tabl: weak_table,
  id referent_id: 原来的对象,
  id *referrer_id: 弱引用指针,
  bool crashIfDeallocating: crashIfDeallocating
*/
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating){
      
     /*
       原对象referent_id打一个标记,变成局部变量referent
     */
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

   ......................省略部分代码......................
    weak_entry_t *entry;
   
   //通过原对象指针去弱引用表中查找,其所对应的弱引用的数组entry
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //获取到了弱引用数组,把新产生的弱引用指针referrer添加到这个数组entry里面
        append_referrer(entry, referrer);
    } 
    else {
        /*
          没获取到弱引用数组,需要重新创建弱引用数组,
          把对应位置置为nil( new_entry.inline_referrers[i] = nil;)
          创造的数组大小有个数的限制,WEAK_INLINE_COUNT这个宏定义的个数是4,
          相当于我们为这个对象新创建的弱引用数组只有四个元素,然后初始化全是nil
         */
        weak_entry_t new_entry;
        new_entry.referent = referent;
        new_entry.out_of_line = 0;
        // 第一个元素添加了弱引用变量
        new_entry.inline_referrers[0] = referrer;
        for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
            // 后三个位置 置为nil
            new_entry.inline_referrers[i] = nil;
        }
        
        weak_grow_maybe(weak_table);
       //把弱引用数组插到弱引用表的对应位置中
        weak_entry_insert(weak_table, &new_entry);
    }
}

weak_entry_for_referent分析:

/*
 通过原对象指针referent到弱引用表当中weak_table,
 找到它对应的弱引用的数据结构的
*/
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);
    /*
     通过弱引用表weak_table,
     获取到弱引用结构体(数组)weak_entries
    */
    weak_entry_t *weak_entries = weak_table->weak_entries;
    if (!weak_entries) return nil;
    /*
     然后通过原对象的指针地址referent,进行哈希算法计算,
     获取到对象在弱引用表中的对应索引位置index
    */
    size_t index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    /*
      下面的while循环实际上是哈希冲突算法
      如果我们在对应位置获取到的对象weak_table->weak_entries[index].referent,
      不是当前所要查找的对象referent,
      会根据冲突算法(index+1) & weak_table->mask进行索引位置index的移动。
      直到找到真正的对应对象的索引位置,
      再将查到的索引位置返回给调用方的数组当中&weak_table->weak_entries[index]  
    */
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    return &weak_table->weak_entries[index];
}

一个weak变量是怎样被添加到弱引用表当中的?

  1. 一个被声明为__weak的一个对象指针,经过编译器的编译之后呢,会调用objc_initWeak方法。
  2. 然后经过一系列的函数调用栈,最终在这个weak_register_no_lock函数中进行弱引用变量的添加。
  3. 添加的位置是通过一个哈希算法来进行位置查找的。
  4. 如果说我们查找对应位置当中已经有了当前对象所对应的弱引用数组,就把新的弱引用变量添加到那个数组当中
    如果没有的话,我们就重新创建一个弱引用数组,然后把第0个位置添加上我们最新的weak指针,后面的都初始化为0/nil

当一个对象被废弃/释放之后,weak变量是如何处理的?
 


weak_clear_no_lock具体方法实现:


/**
   weak_table_t:弱引用表
   id:引用的id,实际上就是dealloc的b那个对象
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    /**
     通过新声明的局部变量referent,在弱引用表weak_table去查找它的对应的弱引用数组entry。
     weak_entry_for_referent函数:添加weak变量的时候也遇到过。
         通过被废弃对象的指针,经过哈希算法的计算,求出弱引用数组对应的数组索引位置。
         通过索引返回给调用方当前对象所对应的弱引用数组。
     */
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    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;
    /**
     如果弱引用变量的个数小于4的话,就取inline_referrers,反之取referrers,
     总之referrersd所取到的,就是最终当前对象对应的所有弱引用指针的数组列表
     */
    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) {
        // *referrer:当前对象曾被修饰过的所有的弱引用指针
        objc_object **referrer = referrers[i];
        if (referrer) {
            //如果 *referrer弱引用指针代表的地址就是被废弃的地址referent的话,就置为nil
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _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);
}

清除weak变量,同时设置指向为nil。

  • 在调dealloc后,经过一系列的调用,在内部最终会调用弱引用清除的相关函数weak_clear_no_lock()(技术引用章节有提到),
  • weak_clear_no_lock内部会根据当前对象指针查找弱引用表,把当前对象相对应的弱引用都拿出来,是一个数组,
    然后遍历数组,遍历所有的弱引用指针,如果弱引用指针代表的地址就是被废弃的地址referent的话,就置为nil。


 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值