Tagged Pointer - ShenYj/ShenYj.github.io GitHub Wiki
在 2013 年 9 月,苹果推出了 iPhone5s,正式开始切换到了64位指令集,随之为了节省内存和提高执行效率,苹果在优化上引入了 Tagged Pointer(小对象)
-
由于 NSNumber、NSDate以及部分NSString 等类的变量本身的值需要占用的内存大小常常不需要 8 个字节
-
苹果将Tagged Pointer引入,给 64 位系统带来了内存的节省和运行效率的提高。Tagged Pointer通过在其最后一个 bit 位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其 isa 变量
-
在objc底层源码中也有很多关于Tagged Pointer特殊处理的地方,比如set、get方法中会忽略对小对象的retain、release等操作,这也是为什么同样是字符串,为什么有些异步频繁操作会造成crash,而一些小对象字符串不会,就是因为异步过多release导致
小对象真实类型可在objc-internal.h
源码中定义的枚举类型了解到
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
// 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_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
#if __has_feature(objc_fixed_enum) && !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
通过内置函数
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
decode 出对应的objc_tag_index_t
值, 即可验证其类型
e.g.
/// 需要引入runtime库 `#import <objc/runtime.h>`
NSString *str = [NSString stringWithFormat:@"b"];
NSLog(@"0x%lx", _objc_decodeTaggedPointer(str));
/// log
0xa000000000000621
结果分析
- decode得出结果
0xa000000000000621
- 十六进制转成二进制得到
0xa
->1010
-
1010
最高位有值,代表其是Tagged Pointer类型 -
1010
去掉最高位的剩余三位 ->010
则代表其真实类型 -
010
对应的值是 2 ->OBJC_TAG_NSString = 2
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
// # define _OBJC_TAG_MASK (1UL<<63)
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
代码分析
-
_OBJC_TAG_MASK
:1左移63位 -> 保留最高位的值 -
(uintptr_t)ptr & _OBJC_TAG_MASK)
-> 取高位 -
((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK
-> 判断高位是否有值
当前源码版本
objc4-781
翻看了目前最新的源码 objc4-818.2
-
在
_objc_isTaggedPointer
函数下新增了一个_objc_isTaggedPointerOrNil
函数static inline bool _objc_isTaggedPointerOrNil(const void * _Nullable ptr) { // this function is here so that clang can turn this into // a comparison with NULL when this is appropriate // it turns out it's not able to in many cases without this return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; }
-
关于
_OBJC_TAG_MASK
之前的版本只是通过条件编译判断了下是
macOS
平台,在最新的818.2
源码中新增对arm64
的新标记
但_OBJC_TAG_MASK
的值最终对我们iOS
平台并没有造成什么改变, 还是通过最高位判断-
OBJC_SPLIT_TAGGED_POINTERS
与OBJC_MSB_TAGGED_POINTERS
#if __arm64__ // ARM64 uses a new tagged pointer scheme where normal tags are in // the low bits, extended tags are in the high bits, and half of the // extended tag space is reserved for unobfuscated payloads. # define OBJC_SPLIT_TAGGED_POINTERS 1 #else # define OBJC_SPLIT_TAGGED_POINTERS 0 #endif #if (TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__ // 64-bit Mac - tag bit is LSB # define OBJC_MSB_TAGGED_POINTERS 0 #else // Everything else - tag bit is MSB # define OBJC_MSB_TAGGED_POINTERS 1 #endif
-
_OBJC_TAG_MASK
#if OBJC_SPLIT_TAGGED_POINTERS # define _OBJC_TAG_MASK (1UL<<63) #elif OBJC_MSB_TAGGED_POINTERS # define _OBJC_TAG_MASK (1UL<<63) #else # define _OBJC_TAG_MASK 1UL #endif
-
两篇文章内容一样,文章中详细的介绍了 Tagged Pointer的优点和注意事项