Tagged Pointer的背景
在64机器中,一个指针占据8个字节,一个对象包含isa指针,也是8个字节。对于包含整形的NSNumber来说,还必须有8个字节存储这个整型数字。所以一个NSNumber类型的对象加上一个指针,至少会占据24个字节。
苹果为了优化对象的内存设计了Tagged Pointer,在64位的机器上,把诸如整型,char类型,或者一些长度较小的字符串,直接放入指针里面,然后在高四位和低四位加上标记位,表示当前的指针为Tagged Pointer并且指明当前的数据类型。这样就可以方便地存储和访问数据了。引入Tagged Pointer后,内存占用会减少一半以上,访问速度会提升3倍。Tagged Pointer并不是对象,它的创建和销毁过程比对象也快很多。以一个整型的NSNumber为例,不使用Tagged Pointer的情况下,至少占用24字节,而使用了Tagged Pointer后,占用的字节数为8个字节,可见,内存方便的提升还是很明显的。
Tagged Pointer支持的类型
常见的数据类型NSString,NSNumber,NSIndexPath,NSDate和UIColor支持Tagged Pointer
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSDate = 6,
OBJC_TAG_UIColor = 17,
Tagged Pointer生成过程
在app启动过程中,dyld链接动态库的时候,会生成Tagged Pointer混淆数字,DisableTaggedPointerObfuscation这个属性默认为NO,也就是说混淆数字默认是存在的。
static void
initializeTaggedPointerObfuscator(void)
{
//DisableTaggedPointerObfuscation为禁用Tagged Pointer混淆
if (!DisableTaggedPointerObfuscation){
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
#if OBJC_SPLIT_TAGGED_POINTERS
// The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
// Shuffle the first seven entries of the tag permutator.
int max = 7;
for (int i = max - 1; i >= 0; i--) {
int target = arc4random_uniform(i + 1);
swap(objc_debug_tag60_permutations[i],
objc_debug_tag60_permutations[target]);
}
#endif
} else {
objc_debug_taggedpointer_obfuscator = 0;
}
}
在应用程序的运行阶段,使用这个混淆数字,生成实际的Tagged Pointer。
static inline void * _Nonnull
_objc_makeTaggedPointer_withObfuscator(objc_tag_index_t tag, uintptr_t value,
uintptr_t obfuscator)
{
if (tag <= OBJC_TAG_Last60BitPayload) {
// ASSERT(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer_withObfuscator(result, obfuscator);
} else {
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer_withObfuscator(result, obfuscator);
}
}
在生成Tagged Pointer的过程中,实际是对指针做了位运算。
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
在程序的运行过程中,如果要判断一个指针是否为Tagged Pointer,只要看标记位就可以了。
Tagged Pointer和对象之间的差异
Tagged Pointer并不是对象,也没有isa指针,内存分配和销毁的过程和对象也不一样。
id objc_retain(id obj)
{
if (_objc_isTaggedPointerOrNil(obj)) return obj;
return obj->retain();
}
void
objc_release(id obj)
{
if (_objc_isTaggedPointerOrNil(obj)) return;
return obj->release();
}
引用计数管理的时候,如果是Tagged Pointer,函数会直接return。
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
//如果是Tagged Pointer,直接返回
if (_objc_isTaggedPointerOrNil(referent)) return referent_id;
//其他代码
//...
}
在修改weak表的时候,如果被弱引用的是Tagged Pointer,这个时候Tagged Pointer不会加入到weak表里面。
// @property (nonatomic, strong) NSString *string;
for (int i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.string = [NSString stringWithFormat:@"hello world!"];
});
}
对于上述代码,如果string是nonatomic修饰,代码执行过程中会发生crash。因为多线程的环境多个线程同时访问读写string,会发生坏地址访问而crash。
// @property (nonatomic, strong) NSString *string;
for (int i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.string = [NSString stringWithFormat:@"hello"];
});
}
这段代码在执行的过程中不会发生crash,因为string此时被初始化为较短的字符串@“hello”,程序在运行过程中会把它优化为Tagged Pointer,在对Tagged Pointer操作的时候实际是对uintptr_t类型的位运算,不涉及对象内存的分配和销毁过程。