1、Tagged Pointer简介
1.1、Tagged Pointer特点
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储。
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值,对象占16个字节,指针8个字节,就需要至少24个字节,如下图所示:
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中(仅用8个字节就可以保存NSNumber的数据),当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
NSNumber *number1 = @4;
NSNumber *number2 = @5;
NSNumber *number3 = @(0xFFFFFFFFFFFFFFFF);
NSLog(@"%p %p %p",number1, number2, number3);
打印结果:
~: 0x8146ba2e9d0ffff5
~: 0x8146ba2e9d0ffef5
~: 0x1058460f0
通过观察打印可以看到,number1、number2都是通过指针来保存的数据,number3是由于指针不过存储数据,使用了动态分配来存储的。
1.2、如何区分Tagged Pointer
区分条件,iOS平台,地址最高有效位是1(第64bit),Mac平台,地址最低有效位是1。
可以查看objc源码中判断是否是TaggedPointer方法:
// iOS平台
# define _OBJC_TAG_MASK (1UL<<63)
// Mac平台
# define _OBJC_TAG_MASK 1UL
// 判断方法
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
iOS平台位运算:
通过位运算,拿到最后一个位 _OBJC_TAG_MASK = 1000 0000
0b 0000 0111
& 0b 1000 0000
---------------
0b 1000 0000
Mac平台位运算:
通过位运算,拿到最后一个位 _OBJC_TAG_MASK = 0b 0000 0001
0b 0000 0111
& 0b 0000 0001
---------------
0b 0000 0001
1.3、Tagged Pointer消息发送
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
2、Tagged Pointer面试题
2.1、执行以下代码会有什么问题
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}
会报坏内存访问错误: ! Thread 5: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
分析:
因为在给self.name赋值时,实际上调用的是[self setName]方法:
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
有多条线程会同时执行[_name release]方法,所以重复执行release方法就会造成坏内存访问。
解决方案:加锁
2.1、执行以下代码会有什么问题
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
如果将字符串改成abc,这段代码就不会崩溃。
分析:
因为abc是通过TaggedPointer方式存储的,是直接赋值,没有调用setName方法,可以通过打印字符串class查看:
NSString *str1 = [NSString stringWithFormat:@"abc"];
NSString *str2 = [NSString stringWithFormat:@"abcdeghijk"];
NSLog(@"%p %p", str1, str2);
NSLog(@"%@ %@", [str1 class], [str2 class]);
打印结果:
~: 0x83bc89a2bc94c942 0x60000107b2e0
~: NSTaggedPointerString __NSCFString