内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

细看objc-weak源码

2018-06-08 19:04 出处:清屏网 人气: 评论(0

本文不看其他,只专注于weak的内部结构实现细节和源码解读,看了网上很多的文章都是贴上一篇 open source 里面的代码,并没有对实现细节进行解释。所以在这篇文章中,主要分为

weak_entry_t、weak_table_t的源码解析,weak_entry_t和weak_table_t的相互关系,以及对应的操作函数。

下文的主要是基于两个对象来说的,一个是被引用的对象,一个是弱引用变量(也就是源代码中大量出现的指向指针的指针)。

我说一下我源码阅读的习惯,先把目光放在头文件中,因为头文件能够给我们一个整体基础结构。弄清楚具体的结构之后,然后再跳到实现文件中去看具体的实现细节。

先交代一下我的编译环境和源代码版本:

编译环境:

Apple LLVM version 9.1.0 (clang-902.0.39.1)

Target: x86_64-apple-darwin17.5.0

Thread model: posix

InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

源代码版本:

objc4-723

头文件类关系和结构分析

我先根据头文件画一个基本的UML类图:

UML类图

DisguisedPtr模板类

先将视线放在weak_entry_t上面,结构weak_entry_t的第一个成员变量是referent,它是一个 DisguisedPtr 类模板实例化之后的变量(点开前面的链接吧,不然我讲不清楚,不然你会骂我的),这个成员其实就是保存被引用的对象。

DisguisedPtr类里面看起来这个类并不复杂,有一个uintptr_t类型的成员变量,由此DisguisedPtr类的对象所占用的内存空间大小也应该为8字节。

public下面主要是构造函数加三大函数中的两个:重载复制运算符,赋值构造函数;由于该类里面并没有涉及到动态new指针变量,所以其析构函数便使用了默认析构函数。除此之外还重载一些其他的操作符。主要看一下私有的两个成员函数:

static uintptr_t disguise(T* ptr) {
  return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
  return (T*)-val;
}

其中 disguise 函数是将指针变量强转为uintptr_t的整形变量,具体怎么伪装呢?就是把该指针指向的内存地址(16进制数据比如:0x7ffeefbff4e8)强制转换为无符号长整型的十进制数据。由于其类型是无符号长整型,因此取负数是数据溢出之后取该类型取值范围内较大的长整型值达到伪装的效果(也就是不好去找到原内存地址)。

unsigned long ul_val = 2;
unsigned long*bitl = &ul_val;
cout<<"ul_val address: "<<bitl<<endl;///0x7ffeefbff4e8
///140732920755432 取负数 -> 18446744069408184208
cout<<"disguise: "<<disguise(bitl)<<endl;
cout<<"undisguise: "<<undisguise(*bitl)<<endl;/// 0xfffffffffffffffe 1111...1110

其作用在源文件的注释中也说了,我通俗总结是:对那些比如leak这种内存检测工具进行伪装,然后这些检测工具可能就不好去跟踪被引用的对象。

weak_entry_t

现在来看一下union的具体内存分布细节,怎么来解释这个问题呢?奉上 objc-weak.h 的源码,打开源码配合文章来看。

union {
        struct {/// 为了方便说明问题,我将该结构取名为:struct1
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 2;
            uintptr_t        num_refs : PTR_MINUS_1;/// num_refs记录的是实际引用数量
            uintptr_t        mask;/// 记录当前referrers数组容器的大小
            uintptr_t        max_hash_displacement;/// 根据hash-key寻找index的最大移动数,这个在后面的append_referrer会讲
        };
        struct {/// 为了方便说明问题,我将该结构取名为:struct2
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
};

首先要有一个概念,union里面的多个成员是共享同一且相同大小的内存空间,在strcut1结构成员中算出其总共所占内存大小为64*4,也就是32个字节。其中我的机器是64位机,我的编译器对于指针类型所占内存大小的ABI实现为64位,而无符号长整型占用的内存大小也为64位。多说一句,在C++中结构和类的内存存储区域好像都是在堆上面,由低地址向高地址生长。

基于此来画出inline_referrers和上面第一个结构大致的内存分布样式(关于inline_referrers的元素类型模板类DisguisedPtr所占内存大小在上面讲DisguisedPtr类时提到了):

在源码中注释也说了:

// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.

out_of_line_ness是和inline_referrers[1]的低2位是等同的, out_of_line_nessnum_refs 使用了位段,一共占用64位(2位和62位)。由于此时已经是结构内存对齐了,所以下一个结构成员mask的内存地址就刚好换行。

上面还提到的0x0b10,它应该是经过DisguisedPtr伪装之后得到的值,并不是实际的等于0b10,一个只占两位内存空间的,怎么也存储不了16位的数据。__out_of_line_ness == 0b10__是标记使用out-of-line的状态。关于这个0b10我没有想清楚它的由来,有知道的同学麻烦告知于我!!!

继续来看该结构的构造函数:

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

在创建weak_entry_t实例的时候,默认是使用inline_referrers的方式来管理对象引用的,并把其余的位上的数据清空。

out_of_line_ness 用来判断使用out_of_line的方式来进行引用管理,这个out_of_line_ness的值主要是依据于被引用的对象,其引用变量的个数决定的,具体的逻辑在下文会讲到。

再看看struct1的referrers成员,看起来是一个指针变量,更具体的说是存储的引用变量数组的起始地址,而这些引用变量指针指向的地址被DisguisedPtr进行了伪装。

到这里我把weak_entry_t的内存分布讲了一遍(具体的含义在上面代码块中的注释里),然后下面来看一下 weak_table_t

weak_table_t

weak_table_t 在头文件中看不出什么特别的内容,但是从源码中可以看出,应该是一个基于C的结构,没有使用C++中结构独有的特性。

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;/// 和weak_entry_t的num_refs概念类似
    uintptr_t mask;///和 weak_entry_t的mask概念类似
    uintptr_t max_hash_displacement;/// 和weak_entry_t的max_hash_displacement概念类似
};

同样的,其weak_entries成员也应该是一个数组,存储着weak_entry_t变量的指针。针对该结构头文件中公开的操作函数有:

id weak_register_no_lock(weak_table_t *weak_table, id referent, 
                         id *referrer, bool crashIfDeallocating);
void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
#if DEBUG
bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
#endif
void weak_clear_no_lock(weak_table_t *weak_table, id referent);

这看不了什么具体的内容,所以针对头文件的解读就到这里。下面去实现文件中看看具体的实现,看看网上为什么都在说的基于Hash表的一个存储结构。 源码地址 ,老规矩,打开这个网页对照着源码来看。

objc-weak具体实现细节

首先看两个hash函数:

static inline uintptr_t hash_pointer(objc_object *key);
static inline uintptr_t w_hash_pointer(objc_object **key);

它们会根据对象的指针(不管是指针还是指向指针的指针)调用一个fast-hash函数来生成一个key,其原理是基于 fast_hash ,而这个key的作用目前我们无从得知。

grow_refs_and_insert函数

继续看源码,下面主要来看看一个很重要的函数:

```

__attribute__((noinline, used))

static void grow_refs_and_insert(weak_entry_t *entry,

objc_object new_referrer) { assert(entry->out_of_line); /

* #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)

* entry->mask用来记录referrers的数量

*/

size_t old_size = TABLE_SIZE(entry);

size_t new_size = old_size ? old_size * 2 : 8;/// 增长一倍的大小

size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;

entry->referrers = (weak_referrer_t *)

分享给小伙伴们:
本文标签: objc-weak

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号