[iOS开发]Tagged Pointer对象
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]Tagged Pointer对象相关的知识,希望对你有一定的参考价值。
自2013年苹果推出iphone5s之后,ios的寻址空间扩大到了64位。我们可以用63位来表示一个数字(一位做符号位)。那么这个数字的范围是2^63 ,很明显我们一般不会用到这么大的数字,那么在我们定义一个数字时NSNumber *num = @100
,实际上内存中浪费了很多的内存空间。当然苹果肯定也认识到了这个问题,于是就引入了Tagged pointer
,Tagged pointer
是一种特殊的“指针”,其特殊在于,其实它存储的并不是地址,而是真实的数据和一些附加的信息。
原有系统的问题
我们先看看原有的对象为什么会浪费内存。假设我们要存储一个 NSNumber
对象,其值是一个整数。正常情况下,如果这个整数只是一个 NSInteger
的普通变量,那么它所占用的内存与CPU 的位数有关,在32位 CPU 下占4个字节,在64位CPU 下是占8个字节的。而指
针类型的大小通常也与 CPU 位数相关,一个指针所占用的内存在32 位 CPU 下为4个字节,在64位 CPU 下是8个字节。
所以,一个普通的 iOS程序,如果没有 Tagged Pointer
对象,从32位机器迁移到 64位机器中,虽然逻辑没有任何变化,但这种 NSNumber
、 NSDate
一类的对象所占用的内存会翻倍。
我们再来看看效率上的问题,为了存储和访问一个 NSNumber
对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失。
Tagged Pointer介绍
Tagged Pointer
为了改进上面提到的内存占用和效率问题,苹果提出了 Tagged Pointer
对象。由于 NSNumber
、NSDate
一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20 多亿(注:231 = 2147483648,另外1位作为符号位),对于绝大多数情况都是可以处理的。
所以我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了 Tagged Pointer
对象之后,64位 CPU 下 NSNumber
的内存图变成了下面这样:
我们先看下下面这段代码:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i ++)
dispatch_async(queue, ^
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
);
NSLog(@"end");
运行结果:崩溃(坏内存访问)
原因分析:
因为setter
方法中,对strong
修饰的属性会有一个retain
和release
的操作。在并发多线程的赋值操作中,都是对_name
指针进行的操作,可能在_name
刚刚被release
后进行赋值操作,这个时候_name
指向的内存地址是已经被释放了,所以造成了坏内存访问崩溃。
- (void)setName:(NSString *)name
[name retain];
[_name relase];
_name = name;
解决办法:
- 将并发执行的任务改为串行执行
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
- 将异步执行改为同步执行
dispatch_sync(queue, ^
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
);
- 将属性改为atomic属性原子性
- 加锁
myClass *test = [[myClass alloc] init];
NSLock *lock = [[NSLock alloc] init];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i ++)
dispatch_async(queue, ^
[lock lock];
test.name = [NSString stringWithFormat:@"abcdefghijklmn"];
[lock unlock];
);
NSLog(@"end");
再看以下代码执行结果是什么,不崩溃了。因为没有用到引用计数的内存管理方法,使用的是Tagged Pointer
。
TaggedPointer 结构
苹果为了安全对其做了编码,runtime内部实现了编码、解码方法,我们看一下:
编码:
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return (void *)ptr;
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
return (void *)value;
里面进行了一系列的位运算。
我们可以试着打印地址:
NSNumber *number1 = [NSNumber numberWithInt:1];
NSLog(@"number1 pointer is %p", number1);
输出:
number1 pointer is 0xbb027d74df7d32ea
可见,这个地址是被编码过的。通过资料的查询,我们可以对其结构有个了解:
- Tagged Pointer 标记:x86最后一位是标记位,arm64最高位是标记位。1表示是Tagged Pointer对象,0表示是普通对象。
- Tag:对象类型标记。x86为
1~3
位,arm64为0~2
。7表示有扩展信息。 - Extended:x86为
4~11
位,arm64为54~62
。用来扩展更多类型。 - payload:有效负载。存储真正的数据(除了标记位、tag以及extended),不过为了安全苹果做了编码。
payload
代表有效负载,源码中有这样的注释:
这里可以看出 需要经过 decoded_obj
加上一些位运算操作得到,说明是一个加密解密的过程,下面我们来看一下 decode
:
static inline uintptr_t
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return value;
#endif
return value ^ objc_debug_taggedpointer_obfuscator;
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
return value;
它是由两个过程融合的。_objc_decodeTaggedPointer_noPermute()
这个函数结束最后返回的 value
等于 value ^ objc_debug_taggedpointer_obfuscator
,而 objc_debug_taggedpointer_obfuscator
代表混淆,在initializeTaggedPointerObfuscator()
里可以看到它的初始化:
if (!DisableTaggedPointerObfuscation && dyld_program_sdk_at_least(dyld_fall_2018_os_versions))
// 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;
#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
是赋随机值。
接下来我们去源码里看看tag
的值和代表的类型:
// 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_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
// When using the split tagged pointer representation
// (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
// the tag and payload are unobfuscated. All tags from here to
// OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
// builder is able to construct these as long as the low bit is
// not set (i.e. even-numbered tags).
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
;
接下来,我们使用系统的方法解析一下地址,按照,看一下它们代表的真实含义吧:
int main(int argc, const char * argv[])
@autoreleasepool
NSNumber *number0 = [NSNumber numberWithInt:0];
NSNumber *number1 = [NSNumber numberWithInt:1];
NSNumber *number2 = [NSNumber numberWithInt:2];
NSNumber *number3 = [NSNumber numberWithInt:3];
NSNumber *number4 = [NSNumber numberWithInt:4];
NSNumber *number50 = [NSNumber numberWithInt:50];
#pragma mark number1
NSLog(@"number1 pointer is %p---PointerValue:==0x%lx", number1, _objc_getTaggedPointerValue((__bridge void *)(number1)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(number1)), _objc_getTaggedPointerTag((__bridge void *)(number1)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(number1))]));
#pragma mark number2
NSLog(@"number2 pointer is %p---PointerValue:==0x%lx", number2, _objc_getTaggedPointerValue((__bridge void *)(number2)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(number2)), _objc_getTaggedPointerTag((__bridge void *)(number2)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(number2))]));
#pragma mark number3
NSLog(@"number3 pointer is %p---PointerValue:==0x%lx", number3, _objc_getTaggedPointerValue((__bridge void *)(number3)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(number3)), _objc_getTaggedPointerTag((__bridge void *)(number3)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(number3))]));
#pragma mark number50
NSLog(@"number50 pointer is %p---PointerValue:==0x%lx", number50, _objc_getTaggedPointerValue((__bridge void *)(number50)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(number50)), _objc_getTaggedPointerTag((__bridge void *)(number50)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(number50))]));
NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"ab"];
NSString *str3 = [NSString stringWithFormat:@"abc"];
NSString *str4 = [NSString stringWithFormat:@"abcf"];
#pragma mark str1
NSLog(@"str1 pointer is %p---PointerValue:==0x%lx", str1, _objc_getTaggedPointerValue((__bridge void *)(str1)));
NSLog(@" %@", binForObjectPointer(str1));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(str1)), _objc_getTaggedPointerTag((__bridge void *)(str1)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(str1))]));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%p", str1]));
#pragma mark str2
NSLog(@"str2 pointer is %p---PointerValue:==0x%lx", str2, _objc_getTaggedPointerValue((__bridge void *)(str2)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(str2)), _objc_getTaggedPointerTag((__bridge void *)(str2)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(str2))]));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%p", str2]));
#pragma mark str3
NSLog(@"str3 pointer is %p---PointerValue:==0x%lx", str3, _objc_getTaggedPointerValue((__bridge void *)(str3)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(str3)), _objc_getTaggedPointerTag((__bridge void *)(str3)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(str3))]));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%p", str3]));
#pragma mark str4
NSLog(@"str4 pointer is %p---PointerValue:==0x%lx", str4, _objc_getTaggedPointerValue((__bridge void *)(str4)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(str4)), _objc_getTaggedPointerTag((__bridge void *)(str4)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(str4))]));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%p", str4]));
#pragma mark indexPath
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:4];
NSLog(@"indexPath pointer is %p---PointerValue:==0x%lx", indexPath, _objc_getTaggedPointerValue((__bridge void *)(indexPath)));
NSLog(@" decode:==0x%lx tag:%hu", _objc_decodeTaggedPointer((__bridge void *)(indexPath)), _objc_getTaggedPointerTag((__bridge void *)(indexPath)));
NSLog(@" %@", getBinaryByHex([NSString stringWithFormat:@"%lx", _objc_decodeTaggedPointer((__bridge void *)(indexPath))]));
return 0;
输出:
可以看出,解析出来的tag
就对应着它们的类型,
以上是关于[iOS开发]Tagged Pointer对象的主要内容,如果未能解决你的问题,请参考以下文章