美文网首页
08--内存管理04--Weak 原理

08--内存管理04--Weak 原理

作者: 修_远 | 来源:发表于2020-07-23 01:05 被阅读0次
    TOC

    weak 分析思路:汇编+源码

    • 打开汇编
    • 定位到 objc_initWeak
    • 全局搜索 objc_initWeak,找到实现的地方
    objc_initWeak
    • 定位到重点函数 storeWeak
    storeWeak
    • 分析到函数重点 storeWeak 的核心实现
    重点流程
    • 定位到重点函数 weak_register_no_lock
    weak_register_no_lock

    weak 修饰原理:流程

    变量说明

    在流程中会一直遇到以下几个变量

    weak_tableweak_table_t 类型,全局弱引用表
    entryweak_entry_t 类型,weak 变量的存储实体
    referentobjc_object * 类型,对象的引用,这里表示被弱引用的对象指针
    referrerobjc_object ** 类型,引用的地址,这里表示弱引用的对象指针的指针

    入口函数

    weak源码流程图

    objc_initWeak
    storeWeak

    1. class_initialize

    条件:如果这个类正在初始化,则应该先初始化class_initialize

    2. weak_unregister_no_lock

    条件:如果有旧的值,则先清理 weak_unregister_no_lock

    1. 非空判断,if (!referent) return;

    2. weak_table 中取 weak_entry_t——弱引用的集合entry

    3. 如果 entry 存在移除旧的弱引用:remove_referrer(entry, referrer);

    4. 如果 entry 中的 referrer 为空,则从 weak_table 中移除这个 entry

      if (empty)
          weak_entry_remove(weak_table, entry);
      

    3. weak_register_no_lock

    条件:如果有新的值,则存储 weak_register_no_lock

    1. 如果在 weak_table 中找到了 弱引用对象referent弱引用的集合enter

      if ((entry = weak_entry_for_referent(weak_table, referent)))
      
    • append_referrer

      • 如果下面条件成功,先扩容再插入
      if (entry->num_refs >= TABLE_SIZE(entry) * 3/4)
          grow_refs_and_insert(entry, new_referrer);
      
      • 保存新的弱引用指针的地址
      // 保存新的弱引用指针的地址
      weak_referrer_t &ref = entry->referrers[index];
      ref = new_referrer;
      // 集合数量 +1
      entry->num_refs++;
      
    1. 如果不存在这个 弱引用的集合enter

      • 创建一个新的 弱引用的集合enterweak_entry_t new_entry(referent, referrer);
        作用1:创建一个新的 弱引用的集合enter
        作用2:保存这个弱引用指针的地址

      • 如果 weak_table 需要扩容,则去扩容:weak_grow_maybe(weak_table);

      • 在 weak_table 中插入这个新的 弱引用的集合enter
        weak_entry_insert(weak_table, &new_entry);

    weak 释放原理:流程

    在ARC机制下,当一个对象的引用计数为0的时候,会向他发送一个 release 消息,而在 release 的实现中,会执行 dealloc 方法,在 dealloc 方法中会执行 weak_clear_no_lock(&table.weak_table, (id)this); 方法来清空weak引用

    if (*referrer == referent) {
        *referrer = nil;
    }
    

    通过 引用的地址 将引用(weak变量)置空

    1. dealloc 流程

    _objc_rootDealloc(self);
    
    --> obj->rootDealloc();
    
    ----> object_dispose((id)this);
    
    ------> objc_destructInstance(obj);
    
    --------> obj->clearDeallocating();
    
    ----------> clearDeallocating_slow();
    
    ------------> if (isa.weakly_referenced) {
    ------------>     weak_clear_no_lock(&table.weak_table, (id)this);
    ------------> }
    

    2. weak_clear_no_lock 流程

    参数
    weak_table:全局弱引用表
    referent:将要被释放的弱引用对象

    1. 获取 弱引用的集合entry
      weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    2. 遍历 集合enter 中的弱引用指针,并置空
      *referrer = nil;
    3. 移除 entry集合
      weak_entry_remove(weak_table, entry);

    3. weak_entry_remove 流程

    1. 释放 entry集合 的引用
      free(entry->referrers);
    2. weak_table计数减一
      weak_table->num_entries;
    3. 重新设置 weak_table 的大小
      weak_compact_maybe(weak_table);

    Weak 修饰原理:内存表

    上面介绍了 weak修饰weak释放 的流程,但他们在内存中到底是怎么存在的,并没有分析到,下面将会进行详细说明

    1. 什么是 weak变量?

    先来看一段代码

    LGTeacher* teacher = [LGTeacher alloc];
    
    __weak id wobjc1 = teacher;
    
    __weak id wobjc2 = teacher;
    
    __strong id sobjc1 = wobjc1;
    
    __strong id sobjc2 = wobjc2;
    
    NSLog(@"--%@, --%@, --%@, --%@,", wobjc1, wobjc2, sobjc1, sobjc2);
    

    输出结果

    --<LGTeacher: 0x1012525f0>, --<LGTeacher: 0x1012525f0>, --<LGTeacher: 0x1012525f0>, --<LGTeacher: 0x1012525f0>,
    

    用 weak 修饰的变量是 weak变量,代码中的 wobjc1wobjc2 是用 weak修饰 的变量,sobjc1sobjc2 使用 strong修饰 的变量,但是我们从输出结果中可以看到,他们的所有值都是一样的,那 weakstrong 还有啥区别呢?

    weak和strong变量的存储方式不一样,存储方式后面再做介绍。

    这四个变量的值都是 teacher,teacher 表示对象的首地址,也就是输出结果中的 0x1012525f0 这个地址。如果不看 __weak__strong,上面的四个表达式就是一个单纯的赋值运算符,将右边的值赋给左边。因为有了这两个修饰符,他们有了不一样的含义,在内存中的存储方式发生了改变。

    引用、对象、类型

    还是上面那段代码。

    引用对象类型
    • 对象:alloc分配的一块内存
    • 引用:teacher-强引用, wobjc1wobjc2 -弱引用,sobjc1sobjc2 -强引用
    • 类型:id、LGTeacher,都是类型。在对类的结构的探索中,我们可以直接拿到对象的某个属性的地址,但在开发中,我们并不能直接通过这个地址去修改属性的值,只能通过变量根据类型的setter方法去设置属性的值,这是对内存的一种保护机制。

    下面开始来介绍表的原理

    3. weak 修饰原理:内存表

    例如:程序在执行下面这一行代码的时候,会访问哪些内存相关的操作,都在下面的流程图中。

    __weak id weakRef = objc
    

    referent:weakRef,表示这个弱引用变量
    referrer:&weakRef,表示这个弱引用变量的地址

    weak内存流程图

    变量

    weak_tableweak_table_t 类型,全局弱引用表
    entryweak_entry_t 类型,weak 变量的存储实体
    referentobjc_object * 类型,对象的引用,这里表示被弱引用的对象指针
    referrerobjc_object ** 类型,引用的地址,这里表示弱引用的对象指针的指针

    大致流程

    1. 先从 SideTable 中找到 weakTable;
    2. 然后从 weakTable 中找到 entry,如果没有就新建一个;
    3. 将 weak 引用存入到 entry中;

    lldb探索

    1. 第一次进来:weak_table 中是空的
    image
    1. 执行 else 流程之后,weak_table 中出现了一个 entry
    image
    1. 第二次进来:entry 中有值存在

    第一次存成功了,既然是第二次进来,那不把第一次存的东西找到,肯定是浑身难受的

    • 输出 entry 信息:p *entry

      entry
    • 输出 referent,weak_entry_t 的key值,对象的首地址:p undisguise(18446744069395690144)

      referent
    • 输出 referrers 中的 weak_referrer_t: p $16.referrersp $16.inline_referrers[0]。注释中解释了这个值进行了一些操作,比如指针对齐,所以无法找到二级地址以及一级地址,这个问题后面再探索。

      weak_referrer_t
    1. insert 之后,entry 中有两个值。

      image

    通过上面这一波操作,weak变量已经从隐身状态显型了,呈现到了我们的眼前,再也不是仅仅存在脑海中的幻象了。

    为什么 entry 中存的是 referrer(引用的地址)?

    大家应该都听过一句话——weak变量会自动置为nil,原理就在这个 referrer 中。

    通过 引用的地址 将引用(weak变量)置空

    4. 弱引用表:weak_table_t

    1. weak_table_t 结构

    /**
     * The global weak references table. Stores object ids as keys,
     * and weak_entry_t structs as their values.
     */
    struct weak_table_t {
        weak_entry_t *weak_entries;
        size_t    num_entries;
        uintptr_t mask;
        uintptr_t max_hash_displacement;
    };
    

    注释中说的很明白了

    • 这是一个全局的弱引用表
    • key:对象id 作为 key
    • value:weak_entry_t 结构作为 value

    2. weak_entry_t 的查找流程

    static weak_entry_t *
    weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
    {
        size_t begin = hash_pointer(referent) & weak_table->mask;
        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);
            hash_displacement++;
            if (hash_displacement > weak_table->max_hash_displacement) {
                return nil;
            }
        }
        
        return &weak_table->weak_entries[index];
    }
    

    对于 hash 表来说,重点就是下标的计算,计算 weak_table 中下标的代码:

    size_t begin = hash_pointer(referent) & weak_table->mask;
    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);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    

    计算完下标之后,返回 weak_entry_t 的指针,以便外部可以修改,进行插入或删除的操作

    return &weak_table->weak_entries[index];
    

    5. weak实体表:weak_entry_t

    1. weak_entry_t 源码

    struct weak_entry_t {
        DisguisedPtr<objc_object> referent;
        union {
            struct {
                weak_referrer_t *referrers;
                uintptr_t        out_of_line_ness : 2;
                uintptr_t        num_refs : PTR_MINUS_2;
                uintptr_t        mask;
                uintptr_t        max_hash_displacement;
            };
            struct {
                // out_of_line_ness field is low bits of inline_referrers[1]
                weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
            };
        };
    
        bool out_of_line() {
            return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
        }
    
        weak_entry_t& operator=(const weak_entry_t& other) {
            memcpy(this, &other, sizeof(other));
            return *this;
        }
    
        weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
            : referent(newReferent)
        {
            inline_referrers[0] = newReferrer;
            for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
                inline_referrers[i] = nil;
            }
        }
    };
    

    可以看出来,这里并没有做多少运算的操作,主要是两个属性,

    2. 属性 referent

    这是保存对象地址的一个属性,作为 hash 表的 key 值。

    DisguisedPtr<objc_object> referent;
    

    3. 属性 referrers

    这是一个联合体类型,存的就是引用的地址(二级指针)。

    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    

    4. weak_referrer_t:DisguisedPtr 类

    • weak_referrer_t 类型

      typedef DisguisedPtr<objc_object *> weak_referrer_t;
      
    • DisguisedPtr 类:提供两个静态方法,分别是:

      • 将地址取反转成整型存储,这里可以理解为 encode
      • 对存储的整型取反转成指针返回,这里可以理解为 decode
      template <typename T> // 泛型
      class DisguisedPtr {
          uintptr_t value;
          // encode
          static uintptr_t disguise(T* ptr) {
              return -(uintptr_t)ptr;
          }
          // decode
          static T* undisguise(uintptr_t val) {
              return (T*)-val;
          }
          ……   
      }
      

      其实在上面输出 referent 属性的时候,就用到了这个 decode 操作,

      referent

      苹果这么做的目的是,为了不让 leak 跟踪这些 weak 地址。暂时还没有找到比较有说服力的理由,后面遇到了再补充。

    5. append_referrer 方法

    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
    

    这个方法跟上面的 weakTable 找 entry 的方法流程是一致的:

    1. 计算下标 index
    2. index 处插入 weak_referrer_t

    weak 原理总结

    1. 搞清楚引用、对象、类型这些名词的含义;
    2. 搞清楚 weak 存储的流程;
    3. 搞清楚 weak 释放的流程;
    4. 搞清楚 weak 是怎么存储到内存中的,是存在内存的什么地方;
    5. weak实体存的是 weak变量的地址,为了在释放的时候能够将weak变量置空;
    6. weak实体在存 weak变量的地址的时候,进行了一步取反操作,读的时候再取反读;

    相关文章

      网友评论

          本文标题:08--内存管理04--Weak 原理

          本文链接:https://www.haomeiwen.com/subject/amrflktx.html