美文网首页
iOS 关联对象剖析

iOS 关联对象剖析

作者: Johnny_Z | 来源:发表于2020-10-22 01:44 被阅读0次

一、关联对象 和 分类category

通过分类的加载原理;我们知道:分类中无法添加成员变量;添加属性也无法生成setter和setter方法。如果想在分类中强行添加方法,使用runtime的关联对象是现在常见的一种解决方式。
下面就是在分类中添加关联对象的方式
MCPerson+Test.h:

#import "MCPerson.h"

@interface MCPerson (Test)

@property(nonatomic,copy) NSString *name;

@end

MCPerson+Test.m:

#import "MCPerson+Test.h"
#import <objc/runtime.h>

@implementation MCPerson (Test)

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,"name", name, OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
   return objc_getAssociatedObject(self, "name");
}
@end

二、关联对象实现原理

我们查看objc_setAssociatedObject方法的实现

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);
}

看看SetAssocHook

static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};

进一步查看_base_objc_setAssociatedObject

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

可以看到所有的关联对象都是由AssociationsManager 这个对象管理的

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

AssociationsManager里面有一个静态的_mapStorage,他是一个ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>化名为Storage这样的类型,这里面存放的是AssociationsHashMap一个键值对的结构,可通过get()方法取到地址

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

AssociationsHashMap 类型解释如下

  • key : 是被关联对象的地址DisguisedPtr<objc_object>
  • value : 是另一个ObjectAssociationMap,也是个键值对
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

ObjectAssociationMap 类型解释如下

  • key : 被关联对象名字的指针
  • value : 这是个包含关联对象值和协议的类实例ObjcAssociation
class ObjcAssociation {
    uintptr_t _policy;
    id _value;
public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    ObjcAssociation() : _policy(0), _value(nil) {}
    ObjcAssociation(const ObjcAssociation &other) = default;
    ObjcAssociation &operator=(const ObjcAssociation &other) = default;
    ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
        swap(other);
    }

    inline void swap(ObjcAssociation &other) {
        std::swap(_policy, other._policy);
        std::swap(_value, other._value);
    }

    inline uintptr_t policy() const { return _policy; }
    inline id value() const { return _value; }

    inline void acquireValue() {
        if (_value) {
            switch (_policy & 0xFF) {
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break;
            }
        }
    }

    inline void releaseHeldValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
            objc_release(_value);
        }
    }

    inline void retainReturnedValue() {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
            objc_retain(_value);
        }
    }

    inline id autoreleaseReturnedValue() {
        if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
            return objc_autorelease(_value);
        }
        return _value;
    }
};

其具体对应的结构图如下


image.png

三、关联属性的销毁

在OC对象的dealloc逻辑里面


image.png
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();
    }
}

可以看到在OC对象销毁的时候,会判断有没有关联属性,如果存在关联属性会将关联属性全部移除掉,所以不需要我们特别的做清理工作。

相关文章

  • iOS 关联对象剖析

    一、关联对象 和 分类category 通过分类的加载原理[https://www.jianshu.com/p/6...

  • iOS关联对象技术原理

    iOS关联对象技术原理 iOS关联对象技术原理

  • iOS底层原理总结 - 关联对象实现原理

    iOS底层原理总结 - 关联对象实现原理 iOS底层原理总结 - 关联对象实现原理

  • iOS Objective-C 关联对象

    iOS Objective-C 关联对象 1. 关联对象简介 对于关联对象,我们熟悉它的地方就是给分类添加属性。虽...

  • iOS对象关联

    什么是关联对象 关联对象是指某个OC对象通过一个唯一的key连接到一个类的实例上。 举个例子:xiaoming是P...

  • iOS:关联对象

    目录一,添加属性二,基本知识三,底层原理四,注意点 一,添加属性 1,在类中添加属性,系统会自动生成带下划线的成员...

  • iOS 关联对象

    在平时的工作中经常碰到给类别添加属性的操作,那么实现思路是怎么样的呢? 代码实现:新建一个Person类和Pers...

  • iOS 关联对象

    可以不改变源码的情况下增加实例变量。可与分类配合使用,为分类增加属性。(类别是不能添加成员变量的(property...

  • iOS 关联对象

    关联对象简单的说就是运用oc语言的运行时特性(runtime),给类别加属性(当然不止加属性).正常的类中创建一个...

  • iOS 关联对象

    在上一篇文章中我们理解了load&&initialize,Category---为什么只能加方法不能加属性[htt...

网友评论

      本文标题:iOS 关联对象剖析

      本文链接:https://www.haomeiwen.com/subject/ffnymktx.html