美文网首页
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 原理

    weak 分析思路:汇编+源码 打开汇编 定位到 objc_initWeak 全局搜索 objc_initWeak...

  • MRC、ARC内存管理机制

    MRC、ARC内存管理机制?(为什么要进行内存管理, 内存管理的范围和对象, 内存管理的原理) ** (为什么)...

  • iOS/OS X内存管理(一):基本概念与原理

    iOS/OS X内存管理(一):基本概念与原理 iOS/OS X内存管理(一):基本概念与原理

  • iOS 内存管理

    内存管理的原理 iOS 内存管理,是基于引用计数来管理内存;当对象引用计数为0时,对象将被销毁,回收内存空间;内存...

  • 内存管理原理

    MRC auto-release auto-release :在这一轮 run loop 中我们先不释放这个对象,...

  • 08--内存管理--大话自动释放池

    思考:一个对象什么时候加入自动释放池? How AutoreleasePool 自动释放池是一个抽象的概念 自动释...

  • 内存管理解析

    前言 今天我们大致分析下内存管理相关的底层原理等知识点,分为包括内存布局和内存管理方案两大块,其中内存管理方案会重...

  • ios内存管理机制2016

    1.说一说对内存管理的理解?(原理)iOS内存管理机制的原理是引用计数,引用计数简单来说就是统计一块内存的所有权,...

  • 2019-01-14 day16 !!!python内存管理

    内存管理 1.数据的存储 2.内存释放(垃圾回收机制)原理:

  • OC内存管理

    OC内存管理 一、基本原理 (一)为什么要进行内存管理。 由于移动设备的内存极其有限,所以每个APP所占的内存也是...

网友评论

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

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