# 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](/files/-Mg1MO3QIqA1L4jdrwF4)

实际调用了 `_object_set_associative_reference`

![\_object\_set\_associative\_reference](/files/-Mg1MO3TpGTlS3AdqiDH)

### 2.1 AssociationsHashMap

![AssociationsHashMap](/files/-Mg1MO3V_MH3dVFJIC5d)

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

![AssociationsManager](/files/-Mg1MO3XGPGB1HfKA54K)

可以看出它是一个 `全局静态` 的 `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](/files/-Mg1MO3ZNBejOUca8nC6)

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

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

![try\_emplace](/files/-Mg1MO3avei1a1J_XmvG)

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

#### 触发set

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

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

![第一次调用set方法](/files/-Mg1MO3c0d7ouiOhl2mC)

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

![第二次调用set方法](/files/-Mg1MO3dpbuP01Zuz4CP)

### 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();
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/18.-guan-lian-dui-xiang-yuan-ma-jie-xi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
