【iOS】Category的成员变量

611 阅读3分钟

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_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,就相当于是移除关联对象
  • 原理图
    原理