> 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/di-ceng/18.-guan-lian-dui-xiang-yuan-ma-jie-xi.md).

# 18.关联对象源码解析

## 前言

为分类添加关联对象是我们比较常用的，但是你真的了解关联对象么？

> 本文基于 `objc4-818.2` 源码

## 一、 关联对象

由于分类的属性不会生产相关的成员变量，所以我们需要自己实现 `set/get` 方法。如下：

```
// .h

@property (nonatomic, copy) NSString *cateName;


// .m
- (void)setCateName:(NSString *)cateName {
    objc_setAssociatedObject(self, "cateName", cateName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cateName {
    return  objc_getAssociatedObject(self, "cateName");
}
```

那么，`objc_setAssociatedObject` 和 `objc_getAssociatedObject`，究竟做了什么呢？

## 二、 objc\_setAssociatedObject

![objc\_setAssociatedObject](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F1146483eca3c8e60c272a2ce2c1b750d66c07fe3.png?generation=1627832293358214\&alt=media)

实际调用了 `_object_set_associative_reference`

![\_object\_set\_associative\_reference](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa7f9b7c9040042f80b24bb436d59db470f0d8d2f.png?generation=1627832294871996\&alt=media)

### 2.1 AssociationsHashMap

![AssociationsHashMap](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2736fb0ca7480b50d5d911068f8ac1a935a86d5a.png?generation=1627832293230393\&alt=media)

通过断点我们发现了一个 `AssociationsHashMap`。

![AssociationsManager](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F1972d5b5b4824ff0c2d1c0ad718b95db03c03e43.png?generation=1627832293338250\&alt=media)

可以看出它是一个 `全局静态` 的 `Storage` 类型的 `HashMap`。用来保存运行时所有的关联对象的关联关系。

### 2.2 try\_emplace

如果表中没有的话，就将键值对插入表中。

> Inserts key,value pair into the map if the key isn't already in the map. The value is constructed in-place if the key is not in the map, otherwise it is not moved.

![try\_emplace](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F94906a7bd6b960d0c2323d9ac923f2f7f483a3c8.png?generation=1627832299309450\&alt=media)

#### 一次 set 两次调用了 try\_emplace

通过代码可以看出，一次set关联值，会调用两次 `try_emplace`。

![try\_emplace](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4e038422093db382e2a532eb0935198040082737.png?generation=1627832292909265\&alt=media)

* `auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});`
  * 只用来检查是否存在关联对象的 `key` `ObjectAssociationMap{}` 是个空的不做处理
* `auto result = refs.try_emplace(key, std::move(association));`
  * 这里才是真的设置 `value`

#### 触发set

通过调用 `LookupBucketFor` 查找表中是否已经存在了对应的关联对象。

***第一次调用set方法***

![第一次调用set方法](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2d754a9ccab56e93a67dac86cce2bbe89154844b.png?generation=1627832292919390\&alt=media)

***第二次调用set方法***

![第二次调用set方法](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe7498f1ee154d259ca7129f2fa0134170796cc71.png?generation=1627832293351336\&alt=media)

### 2.3 LookupBucketFor

和查找方法缓存的算法基本一致。

### 2.4 InsertIntoBucket

插入表中

```cpp
template <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                        ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);

// first 就是 key
TheBucket->getFirst() = std::forward<KeyArg>(Key);

// second 是Value
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
```

## 三、 关联对象释放

> 思考：在对象 `dealloc` 的时候，会自动清理管理那对象么？

### 3.1 dealloc

我们从 `dealloc` 开始找：

* dealloc
  * \_objc\_rootDealloc
    * rootDealloc
      * 如果有关联对象
      * object\_dispose
        * objc\_destructInstance
          * \_object\_remove\_assocations
            * 来释放关联对象

```cpp
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}
```
