在介绍Tagged Pointer之前,先简单介绍一下ios的内存布局。
这里大部分内容是从其他地方整理搬运而来,加上部分自己的理解,站在巨人的肩膀上,让自己能看的更远。
一、内存布局
整个内存由高到低主要分为五大块:
内核区
:手机内存总共4GB,我们只用到了3GB,剩余1GB给内核区,部分给保留字段。栈区
:函数,方法 ,是由系统编译器自动管理,不需要程序员手动管理堆区
:通过alloc、malloc、block copy等生成对象所分配的内存空间,释放工作由程序员手动管理BSS段
:未初始化的全局变量,静态变量数据段
:初始化的全局变量,静态变量代码段
:编译之后的代码保留区
:
0xc0000000
用计算机兑换为10进制,计算结果为3GB。
不绝对准确的内存首地址:0x6
在堆区,0x7
在栈区,0x1
数据段、BSS内存地址。
问题1:堆区为什么比栈区慢?堆区,先从栈区找到地址,通过地址找到变量(po objc
),最后找到变量指向的堆区空间(po &objc
);栈区,直接通过cpu的寄存器查找。
注意点:
-
static
修饰的静态全局变量只针对文件有效,与类、分类都没有关系。 -
extern
,用于跨文件访问。
二、Tagged Pointer
早在在2013年9月
,苹果推出了iPhone5s
,与此同时,iPhone5s配备了首个采用64位架构的A7双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于64位程序,引入Tagged Pointer
后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升。
一般
8~10位
的小对象,苹果会自动将其转换为Tagged Pointer类型。
2.1 源码分析
他的初始化在main
方法之前,通过_read_images
函数调用initializeTaggedPointerObfuscator
实现,在10.14后又做了处理,不在直接展示值:
static void
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;
}
}
TaggedPointer编码和解码函数:
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
static inline bool
_objc_taggedPointersEnabled(void)
{
extern uintptr_t objc_debug_taggedpointer_mask;
return (objc_debug_taggedpointer_mask != 0);
}
decod和dencode都是异或^
同一个变量,为什么呢?
a = a ^ b;
b = a ^ b;
a = a ^ b;
2.2 指针 + 值
如果打印Tagged Pointer的指针地址,你会发现是一个奇怪的数字和常规的地址表示不一样。
因为实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。Tagged Pointer = 指针
+ 值
。
所以,它的内存并不存储在堆中,也不需要malloc和free,以及引用计数的处理。
这在retain
和release
方法中有很直观的体现。
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
前面提到在10.14后苹果又做了处理,不在直接展示值,如果需要直接展示需要做相应的解码处理:
//引入
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t _objc_decodeTaggedPointer_(id ptr){
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str1 = [NSString stringWithFormat:@"a"];
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str1));
NSNumber *number1 = [NSNumber numberWithInt:1];//@1;
NSLog(@"%@ - %p - %@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
}
打印结果:
[55584:651418] 0xa000000000000611
[55584:651418] __NSCFNumber - 0xf2c99228254ad581 - 1 - 0xb000000000000012
2.3 存储格式 NSNumber & NSString
- (void)testNumber{
NSNumber *charNumber = [NSNumber numberWithChar:'1'];
NSNumber *shortNumber = [NSNumber numberWithShort:1];
NSNumber *intNumber = [NSNumber numberWithInt:1];
NSNumber *longNumber = [NSNumber numberWithLong:1];
NSNumber *floatNumber = [NSNumber numberWithFloat:1.0];
NSNumber *doubleNumber = [NSNumber numberWithDouble:1.0];
NSLog(@"%p - 0x%lx", charNumber, _objc_decodeTaggedPointer_(charNumber));
NSLog(@"%p - 0x%lx", shortNumber, _objc_decodeTaggedPointer_(shortNumber));
NSLog(@"%p - 0x%lx", intNumber, _objc_decodeTaggedPointer_(intNumber));
NSLog(@"%p - 0x%lx", longNumber, _objc_decodeTaggedPointer_(longNumber));
NSLog(@"%p - 0x%lx", floatNumber, _objc_decodeTaggedPointer_(floatNumber));
NSLog(@"%p - 0x%lx", doubleNumber, _objc_decodeTaggedPointer_(doubleNumber));
}
打印结果:
[64055:782611] 0xc0566246b12ba50d - 0xb000000000000310
[64055:782611] 0xc0566246b12ba60c - 0xb000000000000011
[64055:782611] 0xc0566246b12ba60f - 0xb000000000000012
[64055:782611] 0xc0566246b12ba60e - 0xb000000000000013
[64055:782611] 0xc0566246b12ba609 - 0xb000000000000014
[64055:782611] 0xc0566246b12ba608 - 0xb000000000000015
将charNumber十六进制结果转换为二进制:
第64位
:最高位, 说明这个指针是一个Tagged Pointer
第61-63位
:是11(十进制是3),也就是OBJC_TAG_NSNumber(查上面的枚举)
中间56位
:就是真正的值了,0011 0001对应ASCII的1
第1-4位
:NSNumber的类型:char是0、short是1、int是2、float是4
OBJC_MSB_TAGGED_POINTERS
:64-bit的mac,tag存储在LSB(Least Significant Bit 最低位)。其它情况比如64位的真机和模拟器,tag存储在MSB(Most Significant Bit 最高位)。
NSString与NSNumber类似:
第64位
:最高位,说明这个指针是一个Tagged Pointer
第61-63位
:10(十进制是2),也就是OBJC_TAG_NSString
中间56位
:就是真正的值了
第1-4位
:字符串长度
关于具体的计算方式,我没有找到相关的苹果开源文件,但是从
_objc_makeTaggedPointer
方法可以简单了解一下,这个方法做了一系列位运算,但是并不是最终数据。
2.4 面试题
代码如下:
for (int i = 0; i<100000; i++) {
dispatch_async(dispatch_queue_create(0, 0), ^{
self.nameStr = [NSString stringWithFormat:@"学习taggedpointer,我们来了!"];
NSLog(@"%@",self.nameStr);
});
}
这段代码运行会发生问题,但是如果把代码稍作改动:self.nameStr = [NSString stringWithFormat:@"tagged"];,一切都正常了。
为什么?因为两个对象不一样,一个是NSTaggedPointerString,一个是NSCFString。
2.4 总结
- Tagged Pointer 并非真的对象,没有isa指针,只是看起来和对象一致。
- Tagged Pointer 存储在栈中,其存取值,均已tag+data形式。
- Tagged Pointer 不支持retain、release、autorelease、malloc、free等关于对象的内存管理。