64位系统才会有TaggedPointer功能,本意是为了减小内存浪费,提升性能。
TaggedPointer的相关参数的初始化是在libobjc库的_read_images中进行的。
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
初始化混淆参数,系统版本小于 iOS 12,或启用了禁止混淆功能,则不进行混淆。混淆就是随机生成一个数值,用来对TaggedPointer的value进行混淆,保证安全。
objc-env.h中的定义
OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagged pointer optimization of NSNumber et al.")
OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers")
OBJC_DISABLE_TAGGED_POINTERS,是否禁用TaggedPointer。
OBJC_DISABLE_TAG_OBFUSCATION,是否禁止混淆。
当禁用了TaggedPointer,disableTaggedPointers()函数,将TaggedPointer相关的参数都置为0。
注意如果禁止TaggedPointer会在macOS平台的应用上崩溃,原因在libobjc框架里面的注册函数中会判断如果禁止则崩溃。而iOS平台则不会崩溃,因为在调用_objc_registerTaggedPointerClass之前先判断objc_debug_taggedpointer_mask的值,表示是否启用了TaggedPointer。
注:12.1的系统禁用TaggedPointer会崩溃,原因在于libxpc.dylib中_libxpc_initializer直接调用了_objc_registerTaggedPointerClass。
TaggedPointer 类的索引
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_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
这个索引是用来注册TaggedPointer类使用的,比如NSNumber的注册是在CoreFoundation中__CFInitialize中调用CFNumberGetTypeID来完成的。_read_images()先于__CFInitialize()执行
_objc_registerTaggedPointerClass(objc_tag_index_t tag, Class _Nonnull cls)
static Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{
uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_INDEX_SHIFT)
& _OBJC_TAG_INDEX_MASK);
uintptr_t obfuscatedTag = tag ^ tagObfuscator;
// Array index in objc_tag_classes includes the tagged bit itself
#if SUPPORT_MSB_TAGGED_POINTERS
return &objc_tag_classes[0x8 | obfuscatedTag];
#else
return &objc_tag_classes[(obfuscatedTag << 1) | 1];
#endif
}
在禁止value混淆时,查看下地址
(__NSCFNumber *) $0 = 0xb000000000000012 (int)1
根据上面的代码流程,当传入的tag为3时,obfuscatedTag为3,而SUPPORT_MSB_TAGGED_POINTERS为0,最后[0x8 | obfuscatedTag]的运算结果是11,16进制的B。也就是objc_tag_classes[11]是NSNumber。
看下getIsa函数
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
最终会走到最下面的else,地址经过运算,>>60后与0xF与操作,也就是最高的四位,即b。和上面的class索引对应。
若在macOS系统上,
0x0000000000000127,第一个函数中的运算结果是(obfuscatedTag << 1) | 1,结果是7,
而0x0000000000000127的低四位就是7。
TaggedPointer的判断
#if TARGET_OS_OSX && __x86_64__
# define __OBJC_TAG_MASK__ 1
#else
# define __OBJC_TAG_MASK__ (1ULL<<63)
#endif
static inline bool
__objc_isTaggedPointer(const void *ptr)
{
return ((intptr_t)ptr & __OBJC_TAG_MASK__) == __OBJC_TAG_MASK__;
}
即在macOS平台判断最低位是否为1,其他平台判断最高位是否为1