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

2020-07-23  本文已影响0人  修_远
TOC

weak 分析思路:汇编+源码

objc_initWeak storeWeak 重点流程 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)))
    
  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,上面的四个表达式就是一个单纯的赋值运算符,将右边的值赋给左边。因为有了这两个修饰符,他们有了不一样的含义,在内存中的存储方式发生了改变。

引用、对象、类型

还是上面那段代码。

引用对象类型

下面开始来介绍表的原理

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 中有值存在

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

  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;
};

注释中说的很明白了

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 类

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变量的地址的时候,进行了一步取反操作,读的时候再取反读;
上一篇 下一篇

猜你喜欢

热点阅读