Category为什么不能直接添加成员变量
观察Category的底层结构,发现并没有内存可以存储成员变量,所以Category并不能直接添加成员变量
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
利用runtime关联对象间接给Category添加成员变量
关联对象基本API
1.添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
2.获得关联对象
id objc_getAssociatedObject(id object, const void * key)
3.移除所有的关联对象
void objc_removeAssociatedObjects(id object)
const void * key的选择
1.static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
2.static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
3.使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
4使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
这里的也可以在objc_getAssociatedObject使用_cmd方法,因为_cmd是get方法的隐式参数
objc_getAssociatedObject(obj, _cmd)
objc_AssociationPolicy的选择

关联对象原理分析
搜索objc_setAssociatedObject
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
进入SetAssocHook.get()方法,发现会调用_base_objc_setAssociatedObject
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
进入_object_set_associative_reference方法
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 将对象作为封装传入
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
object->setHasAssociatedObjects();
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
这个方法里可以观察到4个重要类,带有Map的可以简单理解成字典
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
进入AssociationsManager,这里存储着AssociationsHashMap
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
进入AssociationsHashMap的定义,这里以<objc_object>为Key,ObjectAssociationMap为value,这里的Key就是为objc_setAssociatedObject(id object, ,, _)的第一个参数一致
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
// _object_set_associative_reference方法中关于object的处理
DisguisedPtr<objc_object> disguised{(objc_object *)object};
进入ObjectAssociationMap定义,不难发现这里key,即是objc_setAssociatedObject(_, const void *key, _, _)的第二个参数一致
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
进入ObjcAssociation,发现其存储的值就是是objc_setAssociatedObject(_, _, id value, objc_AssociationPolicy policy)的第三个和第四个参数
class ObjcAssociation {
uintptr_t _policy;
id _value;
...
}
id objc_getAssociatedObject(id object, const void * key)核心源码
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
移除关联对象
方式一:将值objc_setAssociatedObject值设置为nil
进入源码此时会执行associations.erase(refs_it)方法进行擦除,这里仅仅是擦除AssociationsManager中AssociationsHashMap中的ObjectAssociationMap的一个ObjcAssociation;
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
...
if (value) {
...
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
// 擦除
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
方式二:调用void objc_removeAssociatedObjects(id object) 进入源码,不难看出这里是object传入直接移除AssociationsManager中key为object的AssociationsHashMap
void
_object_remove_assocations(id object)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
总结
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个AssociationsManager中
- 设置关联对象为nil,就相当于是移除关联对象
- 原理图
