0x01 TaggedPoint
由来
从64
位开始,iOS
引入了Tagged Pointer
技术,用于优化小对象的存储。
- 主要为了解决两个问题:
- 内存资源浪费
- 访问效率
- 特点:
- 专门用来存储小对象
Tagged Pointer
指针的值不再是堆区地址,而是包含真正的值。所以它不会在堆上再开辟空间了。- 内存读取提升
3
倍,创建比之前快100
多倍,销毁速度更快
0x02 怎么解决内存和效率这两个问题的?
- 未使用
Tagged Pointer
NSNumber
等对象需要动态分配内存、维护引用计数等。
总共的空间= 指针空间 + 堆中分配的空间
- 使用
Tagged Pointer
NSNumber
等对象,只需要分配一个指针即可,这个指针内部会包含这些数据内容。
总空间 = 指针空间
因为不用去用对象的方式管理引用计数,所以省却了retain
,release
操作。
-
NSTaggedPointer
支持的类型有哪些呢?
// objc源码 objc-internal.h中
// 支持的类型部分
// 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,
0x03 TaggedPoint类型判断方案
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63) // iOS在最高有效位
#else
# define _OBJC_TAG_MASK 1UL // MACOS在最低有效位
注意:
之前的版本(objc4-723
之前),变量的值直接存储在指针中,很容易的可以读取出来,例如0xb000000000000012
然而现在的版本中(objc4-750
之后),苹果对这个指针做了一些编码处理,不能直接看出来是Tagged Pointer
。
综上,最标准的判断方案就是直接打印内存地址。
在
iOS
系统下,最高位为1
,或者在MAC OS
下最低位为1
。那么便是NSTaggedPoint
类型
0x04 NSNumber的验证
NSNumber *num1 = @1;
NSNumber *num2 = @1234567;
NSNumber *num3 = @12345678;
NSNumber *num4 = @1234567890000000000;
NSNumber *num5 = @13.5;
NSLog(@"%p-%@",num1,num1.class); // 0xd242f6d243c25a83-__NSCFNumber
NSLog(@"%p-%@",num2,num2.class); // 0xd242f6d25114dc83-__NSCFNumber
NSLog(@"%p-%@",num3,num3.class); // 0xd242f6d2ffa31583-__NSCFNumber
NSLog(@"%p-%@",num4,num4.class); // 0x100461f50-__NSCFNumber
NSLog(@"%p-%@",num5,num5.class); // 0x100461fb0-__NSCFNumber
通过以上打印,我们可以发现,打印类型均为__NSCFNumber
,但实际是吗。我们打印其指针地址可以只有最后两个才是对象类型,前面三个都是NSTaggedPointNumber
类型。
// 当前是在MAC OS下测试,看低位
(lldb) p/t 0xd242f6d243c25a83
(unsigned long) $0 = 0b1101001001000010111101101101001001000011110000100101101010000011
(lldb) p/t 0x100461f50
(long) $1 = 0b0000000000000000000000000000000100000000010001100001111101010000
0x05 NSString的验证
// 创建一个常量字符串__NSCFConstantString
NSString *test1 = @"测试数据";
NSString *test2 = [[NSString alloc] initWithString:@"测试数据"]; // 这种现在不推荐使用,推荐直接使用字面量
// 存储在堆区
NSString *test3 = [[NSString alloc] initWithUTF8String:"测试数据"]; // __NSCFString
NSString *test4 = [NSString stringWithFormat:@"测试:%d",1]; // __NSCFString
NSString *test5 = [[NSString alloc] initWithFormat:@"测试%@",@"hello"]; // __NSCFString
// 当字面值常量的数字,英文字母字符串的长度小于10的时候会自动生成这种类型。
// 如果中间有其他特殊字符可能生成__NSCFString类型
// 内容直接被存放到指针中,当做一种伪对象
NSString *test6 = [NSString stringWithFormat:@"1"]; // NSTaggedPointerString
// 最大9位
NSString *test7 = [NSString stringWithUTF8String:"123456789"];
NSString *test8 = [NSString stringWithFormat:@"abcdefghi"];
// 超出位数限制
NSString *test9 = [NSString stringWithUTF8String:"abcdefghij"]; // __NSCFString
在处理字符串方面,我们打印可以看到明确的NSTaggedPointerString
类名,可以用来判断。
总结
- 通过字面量创建的字符串是个常量
- 通过创建对象的方式,如果字符仅为数字或字母,并且总数小于
10
个会生成NSTaggedPointString
,否则生成CFString
- 如果创建对象是,字符有其他非字母数字字符,则不那么容易判断几个字符生成
NSTaggedPointString
0x06 知识点延伸
// Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
// main.m
Person *p = [Person new];
for (int i = 0; i< 1000;i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
p.name = [NSString stringWithFormat:@"测试%i",i];
});
}
NSLog(@"%@",p.name);
问:以上代码能否正常执行?如果crash,为什么
如果将赋值语句改为下面,那在64位机上是否能执行,为什么?:
p.name = [NSString stringWithFormat:@"%i",i];
参考资料
https://mikeash.com/pyblog/friday-qa-2015-07-31-tagged-pointer-strings.html
http://www.cocoachina.com/articles/13449
网友评论