代码编织梦想

本文较长分三篇按序阅读体验更佳,第四篇为辅助阅读按需看
1.Weak的实现(一)
2.Weak的实现(二)
3.Weak的实现(三)
4.Weak的实现-&SideTables()[oldObj]

带着问题看源码:
1.大家都知道weak的底层实现是一个散列表,那么散列表的结构是什么样的?
2.散列表的key是什么,value是什么,散列函数是怎样的?
3.通过几次查找才能找到对应的弱引用?
4.如何查找弱引用对象的引用计数?
5.一个对象对应一个SideTable表而一个SideTable对应多个对象,为什么这样设计?

散列表:散列表(Hash table),根据键直接方法在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

散列函数:它是一个函数,如果把它定义成hash(key),其中key表示元素的键值,在hash(key)的值表示经过散列函数计算得到的散列值

从代码开始

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}

创建weak引用的时候会走到runtimeobjc_initWeak这个方法里面。通过符号断点可以验证。

runtime里的入口

id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

可以看到是走到了

/*
@param `*location`:weak的指针地址
@param `objc_object`:被引用的对象
*/
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating> 
storeWeak(id *location, objc_object *newObj)

{
    //校验旧对象和新对象必须存其一
    ASSERT(haveOld  ||  haveNew);
    //校验如果haveNew=true,newObj不能为nil
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
    //如果weak ptr存在旧值,就取出旧值
        oldObj = *location;
        //以旧对象为析构函数的入参取出旧的SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
     //如果weak ptr是新值,以新对象为析构函数的入参取出对应的SideTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    
    //将oldTable和newTable都上锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    //校验,如果旧值对不上 goto retry
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    //保证弱引用对象的isa非空,防止弱引用机制和+initialize 发生死锁
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            //如果class没有初始化发送+initialized消息
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;
            //到这里class肯定已经初始化了,在走一遍
            goto retry;
        }
    }

    // Clean up old value, if any.
    // 如果weak ptr之前引用了其他对象,在这里清空
    if (haveOld) {
    //<<1>>
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
    //通过newObj和location生成一个新的weak_entry_t并插入到newObj的弱引用数组中(weak_entries)
    //<<2>>
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
           //<<3>> 
           newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

  • 获取对象所在的SideTable
  • isa非空校验,如果isa没有初始化执行class_initialize(cls, (id)newObj);方法
  • 如果地址location的引用对象已经存在,删除其在weak_table_t表中的所有引用
  • 注册新对象的弱引用到weak_table_t表中
  • 设置新对象的弱引用标志符为YES

如果对&SideTables()[oldObj]不太理解的可以先移步这篇文章

1.清除老对象的弱引表

/** 
 * @param weak_table 某个对象的全局weak_table.
 * @param referent 当前对象
 * @param referrer 当前对象的弱引用地址
 */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;//二级指针 weak_ptr的地址

    weak_entry_t *entry;

    if (!referent) return;
    //<<1.1>>查找referent对应的weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //<<1.2>>如果entry存在,删除entry
        remove_referrer(entry, referrer);
        bool empty = true;
        //判断entry的动态数组referrers中是否有值
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            //判断entry的定长数组inline_referrers中是否有值
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        //如果都是空的将entry从weak_table移除
        if (empty) {
            //<<1.3>>
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.

该方法的主要目的是清除存储在entry中的weak_referrer_t,如果发现entry中一个weak_referrer_t也没有,就将整个entryweak_table中移除。

1.1查找referent对应的entry

* @param weak_table 对象的weak_table 
* @param referent 当前对象
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

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

size_t begin = hash_pointer(referent) & weak_table->mask;
  • 通过指针的哈希方法生成的值与weak_table->mask进行BITMASK操作得到一个起始值,这个等下会提一下。
#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
    key ^= key >> 4;
    key *= 0x8a970be7488fda55;
    key ^= __builtin_bswap64(key);
    return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
    key ^= key >> 4;
    key *= 0x5052acdb;
    key ^= __builtin_bswap32(key);
    return key;
}
#endif


  • 每次遍历如果没在weak_entries中找到referent就对index1再进行BITMASK操作。遍历一次就认为是哈希冲突一次并记录在遍历hash_displacement
  • 如果哈希冲突超过了最大值返回nil,当前对象在weak_table中不存在弱引用
  • 成功找到对应的referent就返回相应的weak_entry_t

简单说一下BITMASK技术:
weak的实现中在对weak_entry_tweak_referrer_t的遍历查找都是通过BITMASK来实现的。个人猜测先经过一系列运算得到底位是表示数量的二进制数,在进行BITMASK操作。

在日常开发中用的不多,主要是用于对二进制位进行操作。能快速的得到我们只关心位的值。

value位操作mask结果
0x00000000&0x0000000110
0x00000001&0x0000000111
0x00000010&0x0000000112
0x00000011&0x0000000113
0x00000100&0x0000000110

可以看到如果我们只关心低两位地址,进行BITMASK就能屏蔽其他位。所以上文中的weak_table->mask就是weak_table_tweak_entry_t的最大容量减1(从0开始算)。从static void weak_resize(weak_table_t *weak_table, size_t new_size)方法中可以看出

weak_table->mask = new_size - 1;

理解结构体weak_entry_t

在继续下去之前先看下这个结构体,要注意的是它里面有个联合体,联合体里面的两个结构体共享内存。而他们的区别在于,如果弱引用的数量不大于4个就有定长数组inline_referrers,否则使用动态数组referrers

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;//弱引用对象
    union {//联合体,两种结构体共占有一块内存
        //弱引用数量大于4个用到的结构体
        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;
        };
        //弱引用数量不大于4个用到的结构体
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    
    //判断是否是用的referrers来存储弱引用指针
    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;
        }
    }
};

这个里面存放了某个对象的所有弱引用指针,如果弱引用对象数量不超过四个就报错在结构体数组inline_referrers,否则保存在referrers

1.2删除 entry中的referrer

/** 
 * @param entry 当前对象对应的weak_entry_t
 * @param old_referrer 弱引用指针地址
 */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }

    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    entry->referrers[index] = nil;
    entry->num_refs--;
}

  • 如果定长数组inline_referrers中有值且存在弱引用指针old_referrer,设为nil
  • 如果动态数组referrers中有值且存在弱引用指针old_referrer,设为nil,并将引用数量-1

1.3 如果有必要删除对象的整个弱引用表

**
 * Remove entry from the zone's table of weak references.
 */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    if (entry->out_of_line()) free(entry->referrers);
    bzero(entry, sizeof(*entry));

    weak_table->num_entries--;

    weak_compact_maybe(weak_table);
}

  • 如果使用的是动态数组,释放动态数组的内存
  • entry为起始地址的前sizeof(*entry)个字节区域清零
  • 全局weak_table中,弱引用对象数量-1
  • 收缩表大小
// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Shrink if larger than 1024 buckets and at most 1/16 full.
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
        weak_resize(weak_table, old_size / 8);
        // leaves new table no more than 1/2 full
    }
}

如果weak_table内存占用超过1024字节且内存的1/16比弱引用对象的数量还多就收缩表大小,使其不大于原来的1/2

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/u014219492/article/details/111062840

iOS核心动画:图层的树状结构-爱代码爱编程

这篇文章主要为大家详细介绍了iOSiOS核心动画:图层的树状结构,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。 Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Layer Kit这么一个不怎么和动画有关的名字演变而来,所以做动画这只是Core Animation特性的冰山一角。

新mac安装CocoaPods完整流程-爱代码爱编程

背景 新入职公司,分配一台全新MacBook pro,借此机会梳理一下cocoapods的完整安装流程。 cocoapods安装依赖关系 cocoapods安装需要ruby,更新ruby需要rvm,下载rvm需要gpg,下载gpg需要homebrew,所以安装顺序是homebrew->gpg->rvm->ruby-cocoapods

常见多线程实现,iOS开发程序猿进阶-爱代码爱编程

一 常见多线程实现 (一)pthread (1)特点 1)一套通用的多线程API2)适用于Unix/Linux/Windows等系统3)跨平台可移植4)使用难度大(2)使用语言 C语言 (3)使用频率 几乎不用 (4)线程生命周期 由程序员进行管理 (5)概念、属性与方法 略 (二)NSThread (1)特点 1)使用更加面向对

开发也可以改变下,RxSwift-让你的开发变得简洁高效。-爱代码爱编程

RxSwift到底是什么? RxSwift是一种函数式响应式编程。那什么是函数式编程呢,函数式编程最重要的概念就是“无状态(immutable)”,看到这有些小伙伴可能会很开心,无状态(知名LOL职业选手)嘛,我是他的粉丝!言归正传,到底什么是“无状态(immutable)”呢?我看了很多文章,但是都被他们专业的描述整的一头雾水,我来说说我的看法:有丰富

iOS 边学边记 Weak的实现(二)-爱代码爱编程

正文 接 Weak的实现(一) 2 生成新的weak_entry_t插入到weak_entries中 /** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param we

iOS底层探索--内存管理(上)-爱代码爱编程

兄弟们,最近实在是太忙了。不过~我又回来继续探索了。 内存管理这个名词,我相信所有的iOS工程师都听说过,也是大多数兄弟们,面试最头疼的,今天!小谷带大家走一波源码。希望对大家有所帮助。 关于内存管理,大家都会想到,ARC/MRC、retain、release、dealloc、autorelease。今天就浅谈一波。不对的地方,我在查源码找找,哈哈~

常见多线程实现,iOS开发程序猿进阶-爱代码爱编程

一 常见多线程实现 (一)pthread (1)特点 1)一套通用的多线程API2)适用于Unix/Linux/Windows等系统3)跨平台可移植4)使用难度大(2)使用语言 C语言 (3)使用频率 几乎不用 (4)线程生命周期 由程序员进行管理 (5)概念、属性与方法 略 (二)NSThread (1)特点 1)使用更加面向对

学习编程需要什么基础?从基础到高级?-爱代码爱编程

程序员薪酬高、工作环境好,是很多同学向往的职业,让很多非计算机专业的同学羡慕不已。非计算机专业难道就不能成为程序员了吗? 一、学编程需要什么基础? 1、数学基础 从计算机发展和应用的历史来看计算机的数学模型和体系结构等都是有数学家提出的,最早的计算机也是为数值计算而设计的。因此,要学好计算机就要有一定的数学基础,初学者有高中水平就差不多了。

iOS 音视频开发,AVAudioRecorder实现录音功能!!-爱代码爱编程

AVAudioRecorder、AVAudioPlayer 属于AVFoundation框架,使用时需要先导入**<AVFoundation/AVFoundation.h>**框架头文件。 AVFoundation 是苹果的现代媒体框架,它包含了一些不同用途的 API 和不同层级的抽象。其中有一些是Objective-C

开发也可以改变下,RxSwift-让你的开发变得简洁高效。-爱代码爱编程

RxSwift到底是什么? RxSwift是一种函数式响应式编程。那什么是函数式编程呢,函数式编程最重要的概念就是“无状态(immutable)”,看到这有些小伙伴可能会很开心,无状态(知名LOL职业选手)嘛,我是他的粉丝!言归正传,到底什么是“无状态(immutable)”呢?我看了很多文章,但是都被他们专业的描述整的一头雾水,我来说说我的看法:有丰富

iOS 边学边记 Weak的实现(二)-爱代码爱编程

正文 接 Weak的实现(一) 2 生成新的weak_entry_t插入到weak_entries中 /** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param we

iOS多线程的锁,你知道多少?-爱代码爱编程

前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知半解,于是决定整理一下关于iOS中锁的知识,为大家查缺补漏。 目录 第一部分: 什么是锁 第二部分: 锁的分类 第三部分: 性能对比 第四部分: 常见的死锁 第五部分: 总结(附Demo) 正文 一、什么是锁 在过去几十年并