iOS中的五大内存区域
iOS中的内存区域从低地址到高地址分别为 .text段(代码区)、.data段(已初始化的全局变量、静态变量)、.bss段(未初始化的全局变量、静态变量)、堆区、栈区。
image.png
保留段:用于给系统提供一些必要的空间;
内核区:由系统使用;
这里说明一点:栈区从上往下走,堆区会从下往上走,当两个相遇的时候,则会发生堆栈溢出。
// 一般0x1开头的是 常量 静态 0x7开头的在栈 0x6开头的在堆
NSLog(@"%d - %p",bssA,&bssA); // 0x10efae020 .bss段
NSLog(@"%d - %p",bssB,&bssB); // 0x10efadf50 .data段
int a = 10;
NSLog(@"%p",&a); // 栈 -- 0x7ffee0c51a6c 栈
NSObject *obj = [NSObject new]; // 对象 --
NSLog(@"%@ - %p",obj,&obj); // 0x60000270c170> - 0x7ffee0c51a60 对象在堆上 指针在栈上
NSArray *array = [[NSArray alloc] init];
NSLog(@"%@-%p",array,&array); //()-0x7ffee0c51a58
普通对象查找过程: 先从栈中找到指针,然后去堆中寻找指针对应的内存空间,进而读取到值。在64位机器上,苹果引进了TaggedPointer的概念。
TaggedPointer
为什么要使用taggedPointer
假设要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,在64位CPU下是占8个字节的。1个字节有8位,如果我们存储一个很小的值,会出现很多位都是0的情况,这样就造成了内存浪费,苹果为了解决这个问题,引入了taggedPointer的概念。
计算机位运算
image.png //用位运算交换两个数的值
int a = 2;
int b = 3;
exchange(a, b);
}
void exchange(int a,int b ) { //a = 0000 0010 b = 0000 0011
a = a^b; //a = 0000 0001
b = a^b; //b = 0000 0010 可以看到 a^b^b = a
a = a^b; //a = 0000 0011
printf("a=%d b=%d",a,b);
}
有了上面的基础,我们来看一下taggedPointer的源码
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
if (tag <= OBJC_TAG_Last60BitPayload) {
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(result);
} 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(result);
}
}
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
可以看到,系统对taggedPointer进行了 _objc_encodeTaggedPointer 编码,该编码的实现就是对value进行了 objc_debug_taggedpointer_obfuscator 的异或操作,而在读取taggedPointer的时候,通过 _objc_decodeTaggedPointer 进行解码,还是进行了objc_debug_taggedpointer_obfuscator的异或操作,这样进行了两次异或操作就还原了初始值。
下面我们通过代码来看一下:
NSNumber * num1 = [NSNumber numberWithInt:100];
NSNumber * num2 = [NSNumber numberWithInt:200];
NSLog(@"num1 = %@ - %p",num1,&num1); //num1 = 100 - 0x7ffee0bf5a68
NSLog(@"num2 = %@ - %p",num2,&num2); //num2 = 200 - 0x7ffee0bf5a60
根据打印出来的信息,我们并不能分析出有什么特殊的地方,我们来到taggetpointer的init方法
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;
}
}
可以看到,在Mac10.14和iOS12之前,对taggedpointer做异或的objc_debug_taggedpointer_obfuscator值为0,之后为 objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK这么一步操作,那么我们如何知道系统做了什么呢?
我们来自己实现taggetpointer的decode,来查看系统在decode之后的数据是怎样的:
image.png
然后写一段测试代码:
int num1 = 15;
float num2 = 11;
double num3 = 10;
long num4 = 8;
NSNumber * number1 = @(num1);
NSNumber * number2 = @(num2);
NSNumber * number3 = @(num3);
NSNumber * number4 = @(num4);
NSLog(@"number1 = %@ - %@ - %p - 0x%lx",object_getClass(number1),number1,&number1,_objc_decodeTaggedPointer((number1)));
NSLog(@"number2 = %@ - %@ - %p - 0x%lx",object_getClass(number2),number2,&number2,_objc_decodeTaggedPointer((number2)));
NSLog(@"number3 = %@ - %@ - %p - 0x%lx",object_getClass(number3),number3,&number3,_objc_decodeTaggedPointer((number3)));
NSLog(@"number4 = %@ - %@ - %p - 0x%lx",object_getClass(number4),number4,&number4,_objc_decodeTaggedPointer((number4)));
number1 = __NSCFNumber - 15 - 0x7ffee3683a50 - 0xb0000000000000f2
number2 = __NSCFNumber - 11 - 0x7ffee3683a48 - 0xb0000000000000b4
number3 = __NSCFNumber - 10 - 0x7ffee3683a40 - 0xb0000000000000a5
number4 = __NSCFNumber - 8 - 0x7ffee3683a38 - 0xb000000000000083
可以看到,解码之后的真实的数据并不单单是表示值,以number1为例,0xb0000000000000f2,他的真实的值其实是第二位的f(f转10进制就是15),同理,number2,number3,number4都是如此,最后一位 2、4、5、3分别代表int long float double类型
再来看看string
NSString * str1 = [NSString stringWithFormat:@"a"];
NSString * str2 = [NSString stringWithFormat:@"bb"];
NSString * str3 = [NSString stringWithFormat:@"ccc"];
NSString * str4 = [NSString stringWithFormat:@"dddd"];
NSLog(@"str1 = %@ - %p - 0x%lx",object_getClass(str1),str1,_objc_decodeTaggedPointer(str1));
NSLog(@"str2 = %@ - %p - 0x%lx",object_getClass(str2),str2,_objc_decodeTaggedPointer(str2));
NSLog(@"str3 = %@ - %p - 0x%lx",object_getClass(str3),str3,_objc_decodeTaggedPointer(str3));
NSLog(@"str4 = %@ - %p - 0x%lx",object_getClass(str4),str4,_objc_decodeTaggedPointer(str4));
str1 = NSTaggedPointerString - 0xf5ec647546bfddec - 0xa000000000000611
str2 = NSTaggedPointerString - 0xf5ec647546b9fddf - 0xa000000000062622
str3 = NSTaggedPointerString - 0xf5ec64754089edce - 0xa000000006363633
str4 = NSTaggedPointerString - 0xf5ec647300f99db9 - 0xa000000646464644
最后一位表示长度,61、6262、636363、64646464分别对应ASCII的a,bb,ccc,dddd。
TaggedPointer极大的提高了内存利用率和简化了查询步骤。它不单单是一个指针,还包括了其值+类型,节省了对象的查询流程。
网友评论