runtime底层Class创建的时候回调用到isTaggedPointer
方法,TaggedPointer
这是什么?
下面介绍一下TaggedPointer
:
TaggedPointer是一个能够提升性能, 节省内存的技术. NSString, NSNumber中就采用了这个技术.
对象在内存中都是对齐的, 它们的地址总是指针大小的整数倍, 通常是16的倍数. 在64位系统中, 对象指针是一个64位的整数, 为了对齐, 对象指针的一些位永远是0.
举一个例子, 假设要存储一个NSNumber对象,其值是一个整数. 正常情况下, 如果这个整数只是一个NSInteger的普通变量, 那么它所占用的内存是与CPU的位数有关, 在32位CPU下占4个字节, 在64位CPU下是占8个字节的. 而指针类型的大小通常也是与CPU位数相关, 一个指针所占用的内存在32位CPU下为4个字节, 在64位CPU下也是8个字节. 所以一个普通的iOS程序, 如果没有TaggedPointer对象, 从32位机器迁移到64位机器中后, 虽然逻辑没有任何变化, 但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。
与此同时, 在没有使用TaggedPointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值.
为了改进上面提到的内存占用和效率问题, 苹果提出了TaggedPointer对象. 由于NSNumber, NSDate, NSString等类的变量本身的值需要占用的内存大小常常不需要8个字节, 拿整数来说, 4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位), 对于绝大多数情况都是可以处理的.
在使用TaggedPointer之后, NSNumber指针里面存储的数据变成了: Tag + Data.
因此可以说使用了TaggedPointer以后, 对象指针是一个伪指针, 不再指向对象在堆上的地址, 也就是将数据直接存储在了指针中, 当指针不够存储数据时, 才会使用动态分配内存的方式来存储数据.
如何判断一个对象指针是否是TaggedPointer
我们通过objc判断该对象指针是否是TaggedPointer, 通过查看对象指针的tag bit是否为1来判断, 源码如下:
//如果是iOS平台, 最高有效位是1(第64bit, 使用高位优先规则 MSB)
#define _OBJC_TAG_MASK (1UL<<63)
//如果是mac平台, 最低有效位是1(低位优先规则 LSB)
#define _OBJC_TAG_MASK 1UL
// objc-object.h 中
inline bool objc_object::isTaggedPointer() {
return _objc_isTaggedPointer(this);
}
// objc-internal.h 中
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
结论: 使用对象指针的最高位bit或者最低位bit来标识该指针是否是Tagged Pointer.
为什么可以通过设定最高位或者最低位是否为 1 来标识呢?
因为在对象指针变量分配内存的时候, 都是按2的整数倍来分配的, 这样分配出来的正常内存地址末位不可能为1, 这样通过将最低标识为 1, 就可以和其他正常指针做出区分.
那么为什么最高位为 1, 也可以标识呢?
这是因为64位操作系统, 设备一般没有那么大的内存, 所以内存地址一般只有 48 个左右有效位, 也就是说高位的 16 位左右都为 0, 所以可以通过最高位标识为 1 来表示TaggedPointer. 那么既然一位就可以标识TaggedPointer了其他的信息是干嘛的呢? 我们可以想象, 要有一些bit位来表示这个指针对应的类型, 不然拿到一个TaggedPointer的时候我们不知道类型, 就无法解析成对应的值。
从源码中我们可以有如下注释, 我进行了翻译:
/***********************************************************************
* Tagged pointer objects. - 对于 TaggedPointer 对象的描述
*
* Tagged pointer objects store the class and the object value in the
* object pointer; the "pointer" does not actually point to anything.
* TaggedPointer对象的内容, 是将对象指针(object pointer)用来保存类(class)和对象值(object value)!!! `pointer`指针并没有指向任何对象(obejct), 我们称这个为伪指针!!!
*
* Tagged pointer objects currently use this representation:
* (LSB) 低bit
* 1 bit set if tagged, clear if ordinary object pointer
* 3 bits tag index -- 用来表示 object的class
* 60 bits payload -- 这个负载是 object的class定义的内容(NSString, NSNumber定义的内容不同)
* (MSB) 高bit
* The tag index defines the object's class.
* The payload format is defined by the object's class.
*
* If the tag index is 0b111, the tagged pointer object uses an
* "extended" representation, allowing more classes but with smaller payloads:
* 如果tagindex的内容是`ob111`, 这个`TaggedPointer`对象使用的是`extended`扩展描述法, 这个伪指针可以支持更多的类, 但是对应的用来描述object值的bits减少成52bits
* (LSB) -- 低bit
* 1 bit set if tagged, clear if ordinary object pointer - 用来展示当前对象指针是taggedPointer还是普通对象指针!!!
* 3 bits 0b111 -- 描述是否是 extended tagged pointer
* 8 bits extended tag index -- 扩展 tag index
* 52 bits payload -- 具体的 objct值
* (MSB) -- 高bit
*
* Some architectures reverse the MSB and LSB in these representations.
*
* This representation is subject to change. Representation-agnostic SPI is:
* objc-internal.h for class implementers.
* objc-gdb.h for debuggers.
**********************************************************************/
总而言之:
低地址中最低的1bit, 标志当前对象指针(object pointer)是否是TaggedPointer.
接着3bit位(转化成10进制是 0~7), 标志当前对象是哪个类的内容, 具体标志的内, 通过下面的枚举能看到, 常用的是OBJC_TAG_NSString和OBJC_TAG_NSNumber, 分别是2和3.
如果tagged index == 0b111, 标志extended tag index.
其余的60bit 或者 52bit 就用来存储具体的 object value.(如果tag index对应是NSString, object value 是经过编码的, 参考文章中有具体的编码方法)
这样, 通过TaggedPointer这种伪指针方法, 一次性将class - objectValue放在对象指针中, 即节省内存, 又提高了效率. 当然如果60bit/52bit还不能满足object value的范围, objc仍然会把它当做普通对象指针处理!!!
// 具体 tag index 与类关系如下
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
}
TaggedPointer使用的实例
可以参考文献, 其中有TaggedPointer在NSNumber和NSString中的实践应用.
注意NSString到最后使用成为: NSTaggedPointerString 和 __NSCFString