本文源码来自于 objc4-756.2 版本;

本文研究 sideTable 在 objc4 源码中的使用及其作用,从而解析 iOS 中引用计数器和弱引用的实现原理;

1. retain 操作

我们都知道,新版本的 objc 中引入了 Tagged Pointer,且 isa 采用 union 的方式进行构造,其中 isa 的结构体中有一个 extra_rchas_sidetable_rc,这两者共同记录引用计数器。

直接看看 objc_object::rootRetain() 方法,只看 extra_rc 超出之后 sidetable 相关的代码,删减之后如下:

uintptr_t carry;newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++if (carry) {// Leave half of the retain counts inline and prepare to copy the other half to the side table.transcribeToSideTable = true;newisa.extra_rc = RC_HALF;newisa.has_sidetable_rc = true;
}
if (slowpath(transcribeToSideTable)) {// Copy the other half of the retain counts to the side table.sidetable_addExtraRC_nolock(RC_HALF);
}

那么关键方法就是 sidetable_addExtraRC_nolock()

bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{assert(isa.nonpointer);// 取出this对象所在的SideTableSideTable& table = SideTables()[this];// 取出SideTable中存储的refcnts,类型为Mapsize_t& refcntStorage = table.refcnts[this];// 记录原始的引用计数器size_t oldRefcnt = refcntStorage;// 容错处理assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;uintptr_t carry;size_t newRefcnt = addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);if (carry) {// SideTable溢出处理refcntStorage =SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);return true;} else {// SideTable未溢出refcntStorage = newRefcnt;return false;}
}

这个函数的逻辑如下:

  1. 根据 this,也就是对象的地址从 SideTables 中取出一个 SideTable;
  2. 获取 SideTable 的 refcnts,这个成员变量是一个 Map;
  3. 存储旧的引用计数器;
  4. 进行 add 计算,并记录是否有溢出;
  5. 根据是否溢出计算并记录结果,最后返回;

那么,这里有几个点需要解开:

  1. 什么是 SideTables;
  2. 什么是 SideTable;
  3. 什么是 refcnts;
  4. add 的计算逻辑为什么需要位移?
  5. SideTable 中的溢出时如何处理的?

接下来,一一解决~~~

2. SideTables

直接来看 SideTables 的代码:

static StripedMap<SideTable>& SideTables() {return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

首先,这是个静态函数,返回 StripedMap<SideTable> 类型,但是 & 是什么意思呢?这个是 C++ 语法,表示返回引用类型,看个例子:

&的用法

& 的用法还有些限制,比如不能返回栈中的引用,否则会栈变量消失后会出现 error,还有一些其他的限制,有兴趣可以深究,这里只需要知道 & 表示返回引用类型,也就是可以通过 & func() 来获取函数返回值的指针,其他的不再赘述;

接着,比较懵逼的是 *reinterpret_cast ,其实这个是 C++ 的强制类型转换语法,不用深究,有兴趣的可以自行百度。

所以,总结下这段代码:

  1. SideTables() 使用 static 修饰,是一个静态函数;
  2. & 表示返回引用类型;
  3. reinterpret_cast 是一个强制类型转换符号;
  4. 函数最终的结果就是返回 SideTableBuf;

那么 SideTableBuf 又是什么?

3. SideTableBuf

直接看代码:

// We cannot use a C++ static initializer to initialize SideTables because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to this struct because of the extra indirection.
// Do it the hard way.alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)];

首先看注释,说明了两点:

  1. SideTables 在 C++ 的 initializers 函数之前被调用,所以不能使用 C++ 初始化函数来初始化 SideTables,而 SideTables 本质就是 SideTableBuf;
  2. 不能使用全局指针来指向这个结构体,因为涉及到重定向问题;

其实还是比较懵逼为什么 SideTableBuf 要这么设计,原理有待考究~~~估计和初始化有关;

继续看 SideTableBuf,要点包括:

  1. alignas 表示对齐;
  2. StripedMap<SideTable> 的 size 为 4096(存疑,待验证);
  3. uint8_t 实际上是 unsigned char 类型,即占 1 个字节;

由此可以得出:

  • SideTableBuf 本质上是一个长度为 sizeof(StripedMap<SideTable>) 的 char 类型的数组;

同时也可以这么理解:

  • SideTableBuf 本质上就是一个大小为和 StripedMap<SideTable> 对象一致的内存块;

这也是为什么 SideTableBuf 可以用来表示 StripedMap<SideTable> 对象。本质上而言,SideTableBuf 就是指一个 StripedMap<SideTable>对象;

那么接下来就是搞清楚 StripedMap<SideTable> 是个什么东西了......

4. StripedMap<SideTable>

先上代码,删减一些方法之后的代码为:

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATORenum { StripeCount = 8 };
#elseenum { StripeCount = 64 };
#endifstruct PaddedT {T value alignas(CacheLineSize);};PaddedT array[StripeCount];static unsigned int indexForPointer(const void *p) {uintptr_t addr = reinterpret_cast<uintptr_t>(p);return ((addr >> 4) ^ (addr >> 9)) % StripeCount;}public:T& operator[] (const void *p) { return array[indexForPointer(p)].value; }const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; }...省略了对象方法...
}

上述代码的逻辑为:

  1. 根据是否为 iphone 定义了一个 StripeCount,iphone 下为 8;
  2. 源码中 CacheLineSize 为 64,使用 T 定义了一个结构体,而 T 就是 SideTable 类型;
  3. 生成了一个长度为 8 类型为 SideTable 的数组;
  4. indexForPointer() 逻辑为根据传入的指针,经过一定的算法,计算出一个存储该指针的位置,因为使用了取模运算,所以值为 0 - StripeCount;
  5. 后面的 operator 表示重写了运算符 [] 的逻辑,调用了 indexForPointer() 方法,这样使用起来更像一个数组;

至此,SideTables 的含义已经很清楚了:

  • SideTables 可以理解成一个类型为 StripedMap<SideTable> 静态全局对象,内部以数组的形式存储了 StripeCount 个 SideTable;

那么第一个问题已经解决,按照 sidetable_addExtraRC_nolock() 方法中的逻辑,先从 SideTables 数组中取出一个 SideTable,然后进行相关操作,所以现在就来看看 SideTable 是个啥~~~

5. SideTable

struct SideTable {spinlock_t slock;RefcountMap refcnts;weak_table_t weak_table;SideTable() {memset(&weak_table, 0, sizeof(weak_table));}~SideTable() {_objc_fatal("Do not delete SideTable.");}...省略对象方法...
}

可以看到,SideTable 有三个成员变量:

  1. spinlock_t:自旋锁,负责加锁相关逻辑;
  2. refcnts:存储引用计数器的 Map;
  3. weak_table:存储弱引用的表;

自旋锁暂不讨论,来看看 refcnts 的定义:

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

DenseMap 过于复杂,我们看看基类 DenseMapBase 中的部分代码,如下,DenseMapBase中重写了操作符 []:

ValueT &operator[](const KeyT &Key) {return FindAndConstruct(Key).second;}

大意是通过传入的 Key 寻找对应的 Value。而 Key 是 DisguisedPtr<objc_object> 类型,Value 是 size_t 类型。

回到最初的 sidetable_addExtraRC_nolock 方法中:

size_t& refcntStorage = table.refcnts[this];

上述代码就是通过 this ,即 object 对象的地址,取出 refcnts 这个哈希表中存储的引用计数器;

refcnts 可以理解成一个 Map,使用 address:refcount 的形式存储了很多个对象的引用计数器;

6. 总结

SideTables 和 SideTable

  1. iphone 中 SideTables() 本质是返回一个 SideTableBuf 对象,该对象存储 8 个 SideTable;
  2. 因为涉及到多线程和效率的问题,必定不可能只使用一个 SideTable 来存储对象相关的引用计数器和弱引用;
  3. Apple 通过对 object 的地址进行运算之后,对 SideTable 的个数进行取模运算,以此来决定将对象分配到哪个 SideTable 进行信息存储,因为有取模运算,不会出现数组溢出的情况;

总结:

  • objc 中当对象需要使用到 sideTable 时,会被分配到 8/64 个全局 sideTables 中的某一个表中存储相关的引用计数器或者弱引用信息;

7. weak_table

继续看弱引用如何实现的,看看 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;
};

注释也说明了 weak_table_t 是一个全局引用表,object 的地址作为 key,weak_entry_t 作为 Value。只不过这个全局引用表有 8 或者 64 个;

那么这个 weak_entry_t 是什么,又是怎么用的呢,弱引用的存储逻辑是怎样的?

image.png

疑问

  1. 为什么 weak 能够自动置位 nil?
  2. 析构函数?

SideTables 的初始化时机和流程

isa 中的 shiftcls 溢出时则调用 sidetable_addExtraRC_nolock 方法将一半的引用计数器存入 sidetable 中

从上面的代码来看,逻辑相对清晰:

  1. SideTables 应该是一个全局性的东西;
  2. 传入 this 取出 当前对象相关的 SideTable;
  3. SideTable 有一个 refcnts 的 map,仍然根据 this 取出这个旧的引用计数器;
  4. 通过 addc 计算,需要位移估计是因为 refcnts 在 sideTable 中的位置有关吧;
  5. 计算完毕,如果没有溢出,则直接新值替换旧值;
  6. 如果发生溢出,则??

接下来 SideTable:

struct SideTable {spinlock_t slock;RefcountMap refcnts;weak_table_t weak_table;...省略对象方法...
}

SideTables 应该是一个全局变量,其定义如下:

static StripedMap<SideTable>& SideTables() {return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

如上,是一个静态方法,获取 StripedMap<SideTable> 类型的变量的

SideTables 的初始化

image.png

image.png

image.png

image.png

image.png

notifyBatchPartial 方法中,state = dyld_image_state_bound 时:

(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
image.png

image.png

image.png


http://www.taodudu.cc/news/show-1912758.html

相关文章:

  • iOS:isa指针
  • iOS底层:PAGEZERO的作用
  • iOS图形学(三):屏幕成像原理
  • iOS图形学(四):iOS中的绘图框架
  • Java基础(一):简介和基础数据类型
  • Java基础(二):面向对象
  • Java:常量池
  • Java基础(三):常用对象
  • Java基础(四):异常处理
  • Java基础(五):多线程
  • Android:权限处理
  • AsyncTask的基本使用
  • 在Nginx中配置SSL证书
  • Base64编码流程
  • Nginx配置基础认证
  • Cookie、Session、Token、RefreshToken
  • JSCore浅析及其在iOS上的使用
  • 编程语言的动态性(Dart和OC对比)
  • iOS:Universal Link
  • AFN中的鉴权
  • openGL ES 教程(二):渲染管线
  • MySQL(2)----DDL语句之增、删、改、查操作
  • MySQL(3)-----DML数据库操作(上)
  • 线性表的基本运算
  • MySQL(4)-----DML数据库操作(下)
  • MySQL(1)----帮助使用
  • MySQL(6)-----数据类型
  • (1)封装JSON数据的三种方式
  • (2)从文件中解析JSON数据
  • (1)I/O流对象-----FileInputStream与FileOutputStream

iOS:SideTable相关推荐

  1. iOS:图片相关(19-05-09更)

    1.图片显示相关 1).图片聊天背景拉伸不失真 2).捏合.双击.下拉缩放 3).Banner.相册 4).动画 2.图片操作相关 1).获取.下载图片(分享.传图片用) 2).保存UIImage到本 ...

  2. iOS:解决pod的Insecure world writable dir问题

    iOS:解决pod的Insecure world writable dir问题 参考文章: (1)iOS:解决pod的Insecure world writable dir问题 (2)https:// ...

  3. iOS:动画(18-10-15更)

    目录 1.UIView Animation 1-1.UIView Animation(基本使用) 1-2.UIView Animation(转场动画) 2.CATransaction(Layer版的U ...

  4. ios:新浪微博iphone客户端

    这算是自己做的第一个比较完整的ios的小应用程序,接触到了很多自己以前没怎么用到的东西,好像coredata,GCD,post发送请求,自定义UITableViewCell等等. 先介绍下这个小应用吧 ...

  5. ios: Undefined symbols

    ios: Undefined symbols ios universal build 2019.4.30 背景 用xcode 做了一个iphone ui demo工程,它依赖另一个 tvm_model ...

  6. iOS:如何在iphone、ipad上安装一些常用命令行命令

    iOS:如何在iphone.ipad上安装一些常用命令行命令 相信对Linux.Unix比较熟悉的朋友,在iphone或 ipad越狱后发现通过Cydia可以安装OpenSSH,一定都想安装上并且通过 ...

  7. 21款数据恢复软件 – PC、安卓、IOS:支持你的各种情况数据恢复

    21款数据恢复软件 – PC.安卓.IOS:支持你的各种情况数据恢复 常见数据恢复场景: 误删除 误格式化 误格装系统 系统数据丢失 提示未格式化 U盘数据丢失 硬盘变成raw格式 分区丢失 回收站清 ...

  8. iOS:iOS开发非常全的三方库、插件、大牛博客等等

    iOS开发非常全的三方库.插件.大牛博客等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章 ...

  9. iOS:CALayer核心动画层

    CALayer:核心动画层 简介: Core Animation 是跨平台的,支持iOS环境和Mac OS X环境 学习核心动画之前,需要先理解CALayer,因为核心动画操作的对象不是UIView, ...

  10. UI调试神器 for ios:Reveal的使用与破解

    移动开发这一块做的最多的无疑就是UI和交互,而UI调试也是移动开发人员经常干的一苦逼事.虽然目前iOS开发工具Xcode集成了UI调试功能(Debug View Hierarchy),但带给我们的却只 ...

最新文章

  1. [ZT]如何取得客户端的Windows登录用户名?
  2. 使用WinINet和WinHTTP实现Http访问
  3. Nginx优化、服务器状态模块(--with-http_stub_status_module 的安装使用)
  4. docker学习笔记16:Dockerfile 指令 ADD 和 COPY介绍
  5. python捕获信号退出_Python捕获信号退出Python中的捕获Ctrl+C/SIGINT,优雅地退出多个进程,python,在,CtrlCSIGINT,并...
  6. 干货!谷歌首席科学家发文阐述“半监督学习革命”,想走出瓶颈先试试这个...
  7. 阿里无人车,正在高速过弯
  8. LaTex中编译时出现“Undefined control sequence. l.178 \newlab”问题
  9. python aiml开发文档_Python AIML搭建聊天机器人
  10. codesmith mysql 模板_CodeSmith for MySQL template
  11. android程序卡死无响应,Android程序未响应(ANR)问题
  12. 网上看到的!!很值得欣赏~~(没耐…
  13. 计算机一级office选择题必背知识点,2017年计算机一级MSOffice考试复习知识点
  14. 基于Java毕业设计在线直播平台源码+系统+mysql+lw文档+部署软件
  15. Mybatis + mysql获取元数据时出现问题以及解决
  16. 一阶常微分方程(二)|全微分方程+线性方程+常数易变法+伯努力方程
  17. TP-Link基于亚马逊云科技部署面向运营商的IoT云管平台
  18. 迈入发展期的信创,更需夯实基础
  19. shell实现除法计算器
  20. 简书上使用markdown

热门文章

  1. Spring事务原理分析(二)--@Transactional事务属性的解析
  2. 并发编程学习之CopyOnWriteArraySet
  3. Linux下安装redis5.0.7
  4. 【前端基础进阶】JS原型、原型链、对象详解
  5. .NET微信扫码支付模式二API接口开发测试
  6. Grafana 安装和使用
  7. 机器学习会成为2017年大数据​分析的瓦解者吗?
  8. python 模拟登录博客园并且自动发布一篇文章
  9. MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Wor
  10. linux子进程知道父进程退出的解决方案