> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/ios/nei-cun-guan-li/01.-cong-yi-ge-mian-shi-ti-kan-taggedpointer.md).

# 01.从一个面试题看TaggedPointer

### 前言

之前我们在[WWDC20-runtime优化](/wiki/ios/di-ceng/06.wwdc20runtime-you-hua.md)中有初步接触到了 `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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-29edac7f189c87b1cf30fff835d2239c16eceb4d%2F01-01.png?alt=media)

这里发现指针类型为 `NSTaggedPointerString` 。明显是一个 `TaggedPointer` 。

**较长的字符串**

![2](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-6836eff52b1408bc12a8bed5751b02e9b1ee4839%2F01-02.png?alt=media)

这里的指针类型为 `__NSCFString` 是一个普通的指针。

#### 1.3 问题

![3](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-b90a7d958f9e034b188069db3fc94adb184a889f%2F01-03.png?alt=media)

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

#### 1.4 思考

根据奔溃的现象，你可能会想到。 `TaggedPointer` 的 `Retain` 和 `Release` 难道和普通的有不同么？

### 二、 源码分析

在 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` `Release` 对 `TaggedPointer` 的特殊处理，也提高了 `TaggedPointer` 的效率，同时在多线程场景下的安全性也比较高。
