Links

01.从一个面试题看TaggedPointer

前言

之前我们在WWDC20-runtime优化中有初步接触到了 TaggedPointer 。本文就讲结合一个面试题加深对它的理解。

一、 一个面试题

分析一下下面的代码会有什么问题
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSString *nameStr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self taggedPointerDemo];
}
- (void)taggedPointerDemo {
self.queue = dispatch_queue_create("Ryukie", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<100000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"Ryukie"];
NSLog(@"%@",self.nameStr);
});
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (int i = 0; i<100000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"RyukieRyukieRyukieRyukieRyukieRyukieRyukieRyukieRyukieRyukie"];
NSLog(@"%@",self.nameStr);
});
}
}
@end

1.1 分析

乍一看本题考验的是多线程读写,很难想到和 TaggedPointer 有关。本题两处中的不同点是字符串的长度而已。而根据我们之前对 TaggedPointer 的了解,其中的 Payload 会用来存储数据。但是 Payload 的长度是有限的,过长的数据就无法完整保存进去。
所以,这里分析两处的指针类型是不同的。

1.2 断点调试

较短的字符串
1
这里发现指针类型为 NSTaggedPointerString 。明显是一个 TaggedPointer
较长的字符串
2
这里的指针类型为 __NSCFString 是一个普通的指针。

1.3 问题

3
在第二段代码运行中出现了野指针的崩溃。

1.4 思考

根据奔溃的现象,你可能会想到。 TaggedPointerRetainRelease 难道和普通的有不同么?

二、 源码分析

在 OC 源码中找到:
- (id)retain {
return _objc_rootRetain(self);
}
进一步找到具体实现:
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
...
}
通过 Retain 的源码,我们可以看出,在判断是 TaggedPointer 之后就直接 return 了,没有进行任何操作。
那么 Release 呢?我们探索一下源码:
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
...
}
发现这里和 Retain 是一样的处理。

三、 问题分析

初步了解了 Retain 和 Release 对与 TaggedPointer 之后,我们就很容易理解上面的面试题所发生的现象了
  • TaggedPointer 的较短的字符串没有进行 Retain Release 所以多线程异步读写过程中没有出现野指针错误
  • TaggedPointer 的较长的字符串,由于进行了多线程异步读写操作,不断的 Retain Release ,所以可能出现野指针错误。

总结

  • TaggedPointer 是用于特定类型的小对象的,并不是那些类型就都是 TaggedPointer 。前提是 Payload 要能够装得下。
  • Retain ReleaseTaggedPointer 的特殊处理,也提高了 TaggedPointer 的效率,同时在多线程场景下的安全性也比较高。