iOS:SideTable
本文源码来自于 objc4-756.2 版本;
本文研究 sideTable 在 objc4 源码中的使用及其作用,从而解析 iOS 中引用计数器和弱引用的实现原理;
1. retain 操作
我们都知道,新版本的 objc 中引入了 Tagged Pointer,且 isa 采用 union 的方式进行构造,其中 isa 的结构体中有一个 extra_rc
和 has_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;}
}
这个函数的逻辑如下:
- 根据 this,也就是对象的地址从 SideTables 中取出一个 SideTable;
- 获取 SideTable 的 refcnts,这个成员变量是一个 Map;
- 存储旧的引用计数器;
- 进行 add 计算,并记录是否有溢出;
- 根据是否溢出计算并记录结果,最后返回;
那么,这里有几个点需要解开:
- 什么是 SideTables;
- 什么是 SideTable;
- 什么是 refcnts;
- add 的计算逻辑为什么需要位移?
- SideTable 中的溢出时如何处理的?
接下来,一一解决~~~
2. SideTables
直接来看 SideTables 的代码:
static StripedMap<SideTable>& SideTables() {return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
首先,这是个静态函数,返回 StripedMap<SideTable>
类型,但是 &
是什么意思呢?这个是 C++ 语法,表示返回引用类型,看个例子:
&
的用法还有些限制,比如不能返回栈中的引用,否则会栈变量消失后会出现 error,还有一些其他的限制,有兴趣可以深究,这里只需要知道 &
表示返回引用类型,也就是可以通过 & func()
来获取函数返回值的指针,其他的不再赘述;
接着,比较懵逼的是 *reinterpret_cast
,其实这个是 C++ 的强制类型转换语法,不用深究,有兴趣的可以自行百度。
所以,总结下这段代码:
SideTables()
使用 static 修饰,是一个静态函数;&
表示返回引用类型;reinterpret_cast
是一个强制类型转换符号;- 函数最终的结果就是返回 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>)];
首先看注释,说明了两点:
- SideTables 在 C++ 的 initializers 函数之前被调用,所以不能使用 C++ 初始化函数来初始化 SideTables,而 SideTables 本质就是 SideTableBuf;
- 不能使用全局指针来指向这个结构体,因为涉及到重定向问题;
其实还是比较懵逼为什么 SideTableBuf 要这么设计,原理有待考究~~~估计和初始化有关;
继续看 SideTableBuf,要点包括:
- alignas 表示对齐;
- StripedMap<SideTable> 的 size 为 4096(存疑,待验证);
- 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]; }...省略了对象方法...
}
上述代码的逻辑为:
- 根据是否为 iphone 定义了一个
StripeCount
,iphone 下为 8; - 源码中
CacheLineSize
为 64,使用 T 定义了一个结构体,而 T 就是SideTable
类型; - 生成了一个长度为 8 类型为
SideTable
的数组; indexForPointer()
逻辑为根据传入的指针,经过一定的算法,计算出一个存储该指针的位置,因为使用了取模运算,所以值为 0 - StripeCount;- 后面的
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 有三个成员变量:
- spinlock_t:自旋锁,负责加锁相关逻辑;
- refcnts:存储引用计数器的 Map;
- 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. 总结
- iphone 中 SideTables() 本质是返回一个 SideTableBuf 对象,该对象存储 8 个 SideTable;
- 因为涉及到多线程和效率的问题,必定不可能只使用一个 SideTable 来存储对象相关的引用计数器和弱引用;
- 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
是什么,又是怎么用的呢,弱引用的存储逻辑是怎样的?
疑问
- 为什么 weak 能够自动置位 nil?
- 析构函数?
SideTables 的初始化时机和流程
isa 中的 shiftcls 溢出时则调用 sidetable_addExtraRC_nolock
方法将一半的引用计数器存入 sidetable 中
从上面的代码来看,逻辑相对清晰:
- SideTables 应该是一个全局性的东西;
- 传入 this 取出 当前对象相关的 SideTable;
- SideTable 有一个 refcnts 的 map,仍然根据 this 取出这个旧的引用计数器;
- 通过 addc 计算,需要位移估计是因为 refcnts 在 sideTable 中的位置有关吧;
- 计算完毕,如果没有溢出,则直接新值替换旧值;
- 如果发生溢出,则??
接下来 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 的初始化
notifyBatchPartial 方法中,state = dyld_image_state_bound 时:
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
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相关推荐
- iOS:图片相关(19-05-09更)
1.图片显示相关 1).图片聊天背景拉伸不失真 2).捏合.双击.下拉缩放 3).Banner.相册 4).动画 2.图片操作相关 1).获取.下载图片(分享.传图片用) 2).保存UIImage到本 ...
- iOS:解决pod的Insecure world writable dir问题
iOS:解决pod的Insecure world writable dir问题 参考文章: (1)iOS:解决pod的Insecure world writable dir问题 (2)https:// ...
- iOS:动画(18-10-15更)
目录 1.UIView Animation 1-1.UIView Animation(基本使用) 1-2.UIView Animation(转场动画) 2.CATransaction(Layer版的U ...
- ios:新浪微博iphone客户端
这算是自己做的第一个比较完整的ios的小应用程序,接触到了很多自己以前没怎么用到的东西,好像coredata,GCD,post发送请求,自定义UITableViewCell等等. 先介绍下这个小应用吧 ...
- ios: Undefined symbols
ios: Undefined symbols ios universal build 2019.4.30 背景 用xcode 做了一个iphone ui demo工程,它依赖另一个 tvm_model ...
- iOS:如何在iphone、ipad上安装一些常用命令行命令
iOS:如何在iphone.ipad上安装一些常用命令行命令 相信对Linux.Unix比较熟悉的朋友,在iphone或 ipad越狱后发现通过Cydia可以安装OpenSSH,一定都想安装上并且通过 ...
- 21款数据恢复软件 – PC、安卓、IOS:支持你的各种情况数据恢复
21款数据恢复软件 – PC.安卓.IOS:支持你的各种情况数据恢复 常见数据恢复场景: 误删除 误格式化 误格装系统 系统数据丢失 提示未格式化 U盘数据丢失 硬盘变成raw格式 分区丢失 回收站清 ...
- iOS:iOS开发非常全的三方库、插件、大牛博客等等
iOS开发非常全的三方库.插件.大牛博客等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章 ...
- iOS:CALayer核心动画层
CALayer:核心动画层 简介: Core Animation 是跨平台的,支持iOS环境和Mac OS X环境 学习核心动画之前,需要先理解CALayer,因为核心动画操作的对象不是UIView, ...
- UI调试神器 for ios:Reveal的使用与破解
移动开发这一块做的最多的无疑就是UI和交互,而UI调试也是移动开发人员经常干的一苦逼事.虽然目前iOS开发工具Xcode集成了UI调试功能(Debug View Hierarchy),但带给我们的却只 ...
最新文章
- [ZT]如何取得客户端的Windows登录用户名?
- 使用WinINet和WinHTTP实现Http访问
- Nginx优化、服务器状态模块(--with-http_stub_status_module 的安装使用)
- docker学习笔记16:Dockerfile 指令 ADD 和 COPY介绍
- python捕获信号退出_Python捕获信号退出Python中的捕获Ctrl+C/SIGINT,优雅地退出多个进程,python,在,CtrlCSIGINT,并...
- 干货!谷歌首席科学家发文阐述“半监督学习革命”,想走出瓶颈先试试这个...
- 阿里无人车,正在高速过弯
- LaTex中编译时出现“Undefined control sequence. l.178 \newlab”问题
- python aiml开发文档_Python AIML搭建聊天机器人
- codesmith mysql 模板_CodeSmith for MySQL template
- android程序卡死无响应,Android程序未响应(ANR)问题
- 网上看到的!!很值得欣赏~~(没耐…
- 计算机一级office选择题必背知识点,2017年计算机一级MSOffice考试复习知识点
- 基于Java毕业设计在线直播平台源码+系统+mysql+lw文档+部署软件
- Mybatis + mysql获取元数据时出现问题以及解决
- 一阶常微分方程(二)|全微分方程+线性方程+常数易变法+伯努力方程
- TP-Link基于亚马逊云科技部署面向运营商的IoT云管平台
- 迈入发展期的信创,更需夯实基础
- shell实现除法计算器
- 简书上使用markdown
热门文章
- Spring事务原理分析(二)--@Transactional事务属性的解析
- 并发编程学习之CopyOnWriteArraySet
- Linux下安装redis5.0.7
- 【前端基础进阶】JS原型、原型链、对象详解
- .NET微信扫码支付模式二API接口开发测试
- Grafana 安装和使用
- 机器学习会成为2017年大数据​分析的瓦解者吗?
- python 模拟登录博客园并且自动发布一篇文章
- MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Wor
- linux子进程知道父进程退出的解决方案