TaggedPointer
先看一段代码: - (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.tudou.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"tudou"]; //
NSLog(@"%@",self.nameStr);
});
}
}
复制代码
调用这个方法运行发现能够正常打印以下图:
此时再添加一个方法 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"tudou_好好学习每天向上"];
NSLog(@"%@",self.nameStr);
});
}
}
复制代码
点击屏幕发现崩溃了
其实崩溃反而更好理解,咱们知道在赋值的过程当中调用set
方法是须要对新值的retain
和旧值的release
,可是此时是多线程的而且也没有加锁作线程安全,因此就会出现多个线程同时访问这个变量的状况,同时赋值同时release
旧值就形成了过分释放的问题因此崩溃,可是第一种状况就奇怪了没有崩溃,此时分别下断点查看两种赋值状况下nameStr
的类型
发现第一种状况nameStr
的类型是NSTaggedPointerString
也就是小对象类型,而第二种状况是NSCFString
也就是字符串类型。
这里就引伸出来TaggedPointer
小对象类型。那么一样是经过stringWithFormat
方法建立字符串为何第一种状况是小对象类型而第二种状况不是呢,又为何第一种状况不会崩溃,第二种状况会崩溃呢?带着问题咱们能够先来了解一下什么是小对象类型。
- 源码探索小对象类型 首先建立一个小对象类型打印它的地址以下图:
发现这个小对象的地址是0x8b168fada8723f5a
,按照内存五大区文章中的内存分段发现不知道是归属于那一块
在看_read_images
函数中有个initializeTaggedPointerObfuscator
函数,查看该函数的源码发现是初始化小对象类型混淆器,这里的作法就是先与上_OBJC_TAG_MASK
(ios12之后)
而后全局搜索objc_debug_taggedpointer_obfuscator
发现以下代码
发现了小对象类型的编解码函数,得知底层混淆小对象是进行了异或操做,编码是使用objc_debug_taggedpointer_obfuscator
异或小对象,解码时是用小对象异或解码时objc_debug_taggedpointer_obfuscator
因此上文中打印的小对象的地址是编码后的地址,获得真实小对象的地址则须要解码以下图获得解码后的地址:
发现是0xa000000000000611
发现61
恰好就是a
的ASCII
码,不知道是否是应为凑巧咱们能够多试几个小对象类型:
发现地址中就存在着值那么0xa
、0xb
又是什么呢,此时咱们查看一下判断小对象类型的源码也就是查看_objc_isTaggedPointer
函数的源码发现
发现是经过最高位是不是1来判断是不是小对象类型0xa
、0xb
转为二进制分别是10十、1011
,都是1因此都是小对象类型,后三位主要是用来标记tagType
的0xa
的后三位转成二进制是2,0xb
的后三位转二进制是3,此时咱们再看_objc_makeTaggedPointer
的源码
发现入参就有一个tag
,查看这个tag
的枚举类型
发现2就是字符串的小对象类型,3就是NSNumber
的小对象类型
- 小对象类型不会出现过分释放崩溃的缘由 上文中发现小对象类型的值其实就在地址中,并非存在堆区而是常量区,因此小对象类型的释放是系统处理的,也能够查看
retain
和release
源码发现 
若是是小对象类型直接返回对象了,因此set方法中不存在旧值的释放也就不会存在过分释放崩溃
- 状况1是小对象的缘由 应为状况一复制的是a内存暂用太小,oc优化处理变成小对象类型,占用多大内存是小对象类型,多大又是oc对象了呢以下表:

- 小对象总结
- 小对象并非个真正的对象不存在堆区,是存在常量区的
- 小对象类型不会进入
retain
和release
方法中
- 小对象类型的64位地址中,前4位表明类型,后4位主要适用于系统作一些处理,中间56位用于存储值
- 小对象的有点:应为不存储在堆区因此节省了空间,能够直接进行读取,在内存读取上有着3倍的效率,建立时⽐之前快106倍。