iOS Tagged Pointer存储策略

Tagged Pointer是什么

我们知道,通常情况下,定义一个变量所占用的内存是与CPU的位数有关,比如NSInteger,在32位CPU下占4个字节,在64位CPU下是占8个字节的。但是他们本身的值并不需要这么多的空间来存储,4个字节能表示的有符号整数就有20多亿。所以苹果推出了Tagged Pointer来减少这种内存浪费,它将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。

详情可以看iOS NSString和NSNumber内存分配。这里主要讲下Tagged Pointer的存储策略。

存储策略

来聊一聊tagged pointer是如何存储的。

首先我们创建一些string,并且把它的地址打印出来。

NSMutableString *mutable = [NSMutableString string];
NSString *immutable;
char c = 'a';
for (int i = 0; i < 15; i++) {
      [mutable appendFormat:@"%c", c++];
      immutable = [mutable copy];
      NSLog(@"0x%016lx %@ %@", immutable, immutable, object_getClass(immutable));
 }

这里创建了15个字符串,长度从1到15。我们看一下输出:

0xa000000000000611 a NSTaggedPointerString
0xa000000000062612 ab NSTaggedPointerString
0xa000000006362613 abc NSTaggedPointerString
0xa000000646362614 abcd NSTaggedPointerString
0xa000065646362615 abcde NSTaggedPointerString
0xa006665646362616 abcdef NSTaggedPointerString
0xa676665646362617 abcdefg NSTaggedPointerString
0xa0022038a0116958 abcdefgh NSTaggedPointerString
0xa0880e28045a5419 abcdefghi NSTaggedPointerString
0x000060400022f0c0 abcdefghij __NSCFString
0x00006000002210c0 abcdefghijk __NSCFString
0x000060400022ef20 abcdefghijkl __NSCFString
0x0000600000221160 abcdefghijklm __NSCFString
0x000060400022ee60 abcdefghijklmn __NSCFString
0x000060000004e0a0 abcdefghijklmno __NSCFString

我们发现,长度1到9的时候它都是TaggedPointerString,长度到10以后就是正常的字符串类型了。我们已经知道Tagged Pointer的指针中包含了具体的值,那是怎么保存的呢。我们观察前面这些指针,很容易发现,指针的最后一位数字跟字符串的长度是一致的,指针的第一位都是字母a,并且代表字母的数字是有规律的,a、b、c、d分别对应61、62、63、64。

如果你很熟悉ASCII码,会发现这些数字其实就是对应字母的ASCII码。

苹果在做这些存储时的策略很显而易见了。

  1. 指针地址首位确定类型,是string、date还是number
  2. 如果是字符串,最后一位存储字符串长度
  3. 其余位利用ASCII码编码值来存储字符

那么问题来了,这种存储策略只能存到长度为7的字符串。如上图所示,存到abcdefg的时候,指针地址是0xa676665646362617。到长度为8的时候,指针地址变成了0xa0022038a0116958,已经没有了之前的规律,但是它的类型却依然是TaggedPointerString。说明苹果在字符串长度大于7的时候改变了策略。

在长度是8和9的时候,苹果采用了6比特的编码,创建了一个table:

eilotrm.apdnsIcufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX

我们将abcdefgh这个字符串的指针拿过来,去掉表示类型的头和表示长度的尾,然后将它转化成如下格式:

0xa0022038a0116958 -> 0022038a011695 ->     
001000 100000 001110 001010 000000 010001 011010 010101 ->
8 32 14 10 0 17 26 21

根据最后转化得到的数去查上面的表,可以得到对应的字符串abcdefgh

其实TaggedPointerString的最大长度并不是9,虽然我们刚才打印的例子里,到10就不是了。

刚才我们说了8比特编码(ASCII)策略和6比特编码(查表)策略,其实苹果还做了5比特编码策略,也是通过查表的方式,甚至还是刚才的表,只不过更短:

eilotrm.apdnsIcufkMShjTRxgC4013 

这个表相对上面那个表要少很多字符,这就决定了不是所有的字符串都可以用5比特编码策略,比如我们前面打印的例子,因为出现了b是这个表里没有的,所以长度到10的时候就已经不是Tagged Pointer了。如果我们改一下字符串的内容:

NSString *immutable = @"aaaaaaaaaaa";
NSString *copy = [[immutable mutableCopy] copy];
NSLog(@"0x%016lx %@ %@", copy, copy, object_getClass(copy));
//输出
0xa21084210842108b aaaaaaaaaaa NSTaggedPointerString

我们可以看到,长度为10的string依然可以是Tagged Pointer。如果我们做一下同样的转化操作,再去查表,会得到同样的印证。

其他

除了NSString之外,Tagged Pointer还会用在NSNumber、NSDate、NSIndexPath这些类型,这些类型的存储策略相对来说较为简单。比如NSNumber,除了8位标志位外,其余56位则用来存储数值本身内容。当存储用的数值超过56位存储上限的时候,那么NSNumber才会用真正的64位内存地址存储数值,然后用指针指向该内存地址。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 今天小伙伴问了一个问题,这两个变量地址是否相同? 输出如下: 可以看到这两个对象是常量,所以是存储在常量区,并且地...
    刘宇波V阅读 1,272评论 1 2
  • 这篇文章是参考很多资料才写出来的,有部分内容这几位写的都很详细到位,所以就直接拷贝了,这里向这几位作者学习:深入理...
    晨寂阅读 7,222评论 3 25
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,161评论 1 32
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,688评论 8 265
  • 大家的起点都一样,走着走着,就显现出不同的速度。 同时来学习写作的孩子,有的孩子,每天一篇,非常高产;有的孩子,好...
    呆呆草阅读 92评论 0 0