代码编织梦想

正文 接 Weak的实现(二)

3 设置弱引用标志位

inline void
objc_object::setWeaklyReferenced_nolock()
{
 retry:
    //去对象的isa指针
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    //如果不是non-pointer
    if (slowpath(!newisa.nonpointer)) {
        ClearExclusive(&isa.bits);
        //<<3.1>>
        sidetable_setWeaklyReferenced_nolock();
        return;
    }
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    //弱引用标志位设为1
    newisa.weakly_referenced = true;
    //如果oldisa.bits和newisa.bits不相等返回NO,继续tery里面的内容,这时候newisa.weakly_referenced已经是true了,所以return
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

设置nonpointer类型的isa和非nonpointer类型的isa的弱引用位为1

3.1 设置nonpointerisa指针的弱引用标志位

void 
objc_object::sidetable_setWeaklyReferenced_nolock()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif

    SideTable& table = SideTables()[this];
    table.refcnts[this] |= SIDE_TABLE_WEAKLY_REFERENCED;
}

RefcountMap,也是哈希表

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

keyDisguisedPtr<objc_object>即weak_referrer_t,弱引用对象,valuesize_t,弱引用数量

这里将table.refcnts[this]即最后一位与SIDE_TABLE_WEAKLY_REFERENCED进行位或操作,这时候弱引用标志位变成1

答题
问题1、2、3、5我们放在一起回答

  • 大家都知道weak的底层实现是一个散列表,那么散列表的结构是什么样的?
  • 散列表的key是什么,value是什么,散列函数是怎样的?
  • 通过几次查找才能找到对应的弱引用?
  • 一个对象对应一个SideTable表而一个SideTable对应多个对象,为什么这样设计

大概的讲是一个散列表SideTablesMap,以对象为key,SideTablevalue。散列函数是

static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}

但这只是开始,它取到的是很多对象弱引用表的集合。要想准确的找到某个对象的弱引位置用还要经过两步。

  • 以对象为key,经过一系列运算(位运算,算数运算),最后通过BITMASK找到weak_entries的入口index开始变量,判断对象是否相等。最后才能找到对象所在的weak_entry_t
  • 因为weak_entry_t这里面保存了对象的所有有弱引用,要找到指定的,还要经历和上述类似的操作对比old_referrer才能找到正真的弱引用weak_referrer_t位置。

所以经过3次查找才能找到正真的弱引用。

为什么苹果要创建64SideTable(在iphone上是8个,其他上面是64个),而不是用一个SideTable解决呢。简单的类比,大家都在火车站或者飞机场打过出租车吧,当你前面的队伍黑压压的一片看不到头你是不是希望多开几个出租车上车点。使用多个SideTable也是这个原理提高弱引用的索引速度。

还有一点,我想提下,在整个过程中有两次扩容,一次收缩容量。它们都是动态的。分别是在

  • weak_unregister_no_lock的时候,收缩weak_table_t的容量
// 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
    }
}
  • append_referrer的时候增加weak_entry_t中动态数组*referrers的容量
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                             objc_object **new_referrer)
{
ASSERT(entry->out_of_line());

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 *)
    calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
    
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
    if (old_refs[i] != nil) {
        append_referrer(entry, old_refs[i]);
        num_refs--;
    }
}
// Insert
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
  • weak_grow_maybe的时候,增加weak_table_t的容量
static void weak_grow_maybe(weak_table_t *weak_table)
{
	size_t old_size = TABLE_SIZE(weak_table);

// Grow if at least 3/4 full.
	if (weak_table->num_entries >= old_size * 3 / 4) {
    	weak_resize(weak_table, old_size ? old_size*2 : 64);
	}
}

问题4

如何查找弱引用对象的引用计数?

uintptr_t
_objc_rootRetainCount(id obj)
{
    ASSERT(obj);

    return obj->rootRetainCount();
}

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    //获取isa的比特位
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    //是不是non-pointer
    if (bits.nonpointer) {
        //引用比特位上的引用数加1
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            //判断sidetable是否存在引用计数,如果存在继续相加
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

rootRetainCount源码可以看出,弱引用的引用计数分布在两个地方,一个是isa的比特位中,还有一个在SideTable中。它们相加才是对象的引用计数的数量。

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

AssociatedObject 的源码解析分析底层实现原理-爱代码爱编程

C++ 的析构函数应该理解为内存释放前的清理工作,而不是内存释放,内存释放是使用的 free 函数,还有 OC 的 dealloc 也是,最终真正的释放内存函数是 free,dealloc 也可以理解为是 free 函数调用前做清理工作的。 前言 使用 Category 为已经存在的类添加方法是我们很熟悉的常规操作,但是如果在 Categor

如何使用VIPER构建iOS应用-爱代码爱编程

用VIPER构建iOS应用 为避免撕逼,提前声明:本文纯属翻译,仅仅是为了学习,加上水平有限,见谅! 【原文】https://www.objc.io/issues/13-architecture/singletons/ 用VIPER构建iOS应用 ——by Jeff Gilbert and Conrad Stoll 众所周知,在建筑领域,我们塑造我

iOS上获取崩溃日志的N+1种方法-爱代码爱编程

iOS上获取崩溃日志的N+1种方法 正常情况下,程序崩溃之后都会有崩溃日志保存在我们的手机里面,当崩溃的时候,我们可以通过如下几种方式找到我们的崩溃日志。  方法一:从本机隐私设置里面的里面导出来       当本机发生崩溃的时候,你可以从系统设置->隐私->分析与改进里面找到你当时的崩溃日志,通常,你的崩溃日志格式包含程序包名,时

关于iOS系统你知道多少???-爱代码爱编程

首先来说下ios的历史吧!!! iOS系统诞生于2007年1月1日的Macworld上,当天公布了IOS的初代系统,当时它的名字是iPhone OS X,同时颠覆手机行业的iPhone也横空出世了,它创新的多点触控操作以及极简的用户体验都让全球消费者为之疯狂,3.5英寸的480X320分辨率的大屏幕也远远超过当时手机行业的平均配置,单Home键让 当时标配

iOS 边学边记 深入了解weak底层实现详解-爱代码爱编程

iOS开发者都知道,当一个对象被释放时,所有对这个对象弱引用的指针都会释放并置为nil,那么系统是如何存储这些弱引用对象的呢?又是如何在一个对象释放时,将这些指向即将释放对象的弱引用的指针置为nil的呢?下面我们通过分析SideTable的结构来进一步了解内存管理的弱引用存储细节。 weak结构 在runtime中,有四个数据结构非常重要,分别是Sid

iOS开发实战-第3节-解析json-爱代码爱编程

本节内容 Codable 解码以解析json swift 的异常处理方法 知识点 Codable 可编码 A type that can convert itself into and out of an external representation. 一个可以将自己转换为和脱离外部表示的类型。 通常是创建一个实例对象,在实例对象中创

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

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

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

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

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

关于iOS系统你知道多少???-爱代码爱编程

首先来说下ios的历史吧!!! iOS系统诞生于2007年1月1日的Macworld上,当天公布了IOS的初代系统,当时它的名字是iPhone OS X,同时颠覆手机行业的iPhone也横空出世了,它创新的多点触控操作以及极简的用户体验都让全球消费者为之疯狂,3.5英寸的480X320分辨率的大屏幕也远远超过当时手机行业的平均配置,单Home键让 当时标配

iOS 边学边记 深入了解weak底层实现详解-爱代码爱编程

iOS开发者都知道,当一个对象被释放时,所有对这个对象弱引用的指针都会释放并置为nil,那么系统是如何存储这些弱引用对象的呢?又是如何在一个对象释放时,将这些指向即将释放对象的弱引用的指针置为nil的呢?下面我们通过分析SideTable的结构来进一步了解内存管理的弱引用存储细节。 weak结构 在runtime中,有四个数据结构非常重要,分别是Sid

华彬拟斥3000万元进军新零售,红牛赋能新渠道-爱代码爱编程

上市公司万里石12月9日发布公告,公司拟与华彬投资、深圳瑞麻共同投资设立“厦门华彬万里新零售有限公司”,该公司注册资本为5000万元人民币,其中公司认缴出资额1100万元,占注册资本的22%;华彬投资认缴出资额3000万元,占注册资本的60%;深圳瑞麻认缴出资额900万元,占注册资本的18%。 万里石公告称,本次设立新零售公司主要为顺应市场新的