NSTaggedPoint

2020-04-28  本文已影响0人  哦小小树

0x01 TaggedPoint由来

64位开始,iOS引入了Tagged Pointer技术,用于优化小对象的存储。

  1. 内存资源浪费
  2. 访问效率
  1. 专门用来存储小对象
  2. Tagged Pointer指针的值不再是堆区地址,而是包含真正的值。所以它不会在堆上再开辟空间了。
  3. 内存读取提升3倍,创建比之前快100多倍,销毁速度更快

0x02 怎么解决内存和效率这两个问题的?

// 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类名,可以用来判断。

总结

  1. 通过字面量创建的字符串是个常量
  2. 通过创建对象的方式,如果字符仅为数字或字母,并且总数小于10个会生成NSTaggedPointString,否则生成CFString
  3. 如果创建对象是,字符有其他非字母数字字符,则不那么容易判断几个字符生成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

上一篇 下一篇

猜你喜欢

热点阅读