iOS Objective-C 关联对象

2020-09-27  本文已影响0人  just东东

iOS Objective-C 关联对象

1. 关联对象简介

对于关联对象,我们熟悉它的地方就是给分类添加属性。虽然我们可以在分类中通过@property编写代码来声明一个属性,但是当我们使用的时候就报方法找不到错误,其实缺失的方法就是属性的gettersetter的实现,那么关联对象就可以完美的解决这个问题。

官方定义:

Associative references, available starting in OS X v10.6, simulate the addition of object instance variables to an existing class. Using associative references, you can add storage to an object without modifying the class declaration. This may be useful if you do not have access to the source code for the class, or if for binary-compatibility reasons you cannot alter the layout of the object.

Associations are based on a key. For any object you can add as many associations as you want, each using a different key. An association can also ensure that the associated object remains valid for at least the lifetime of the source object.

译: 从OS X v10.6开始可用的关联引用模拟了将对象实例变量添加到现有类中。使用关联引用,可以在不修改类声明的情况下将存储添加到对象。如果您无权访问该类的源代码,或者由于二进制兼容性原因而无法更改对象的布局,则这可能很有用。

关联基于key。对于任何对象,您都可以根据需要添加任意数量的关联,每个关联都使用不同的key。关联还可以确保关联的对象至少在源对象的生存期内保持有效。

通过苹果官方文档我们可以知道,关联引用不仅仅可以用在给分类添加属性。但是给分类添加属性是我们最常用的场景。

关联对象的两个函数

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);
}
  1. 参数一:id object : 要关联的对象
  2. 参数二:const void *key : 关联使用的key值
  3. 参数三:id value : 关联的值,也就是我们要设置的值。
  4. 参数四:objc_AssociationPolicy policy : 策略属性,以什么形式保存
id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}
  1. 参数一:id object : 获取哪个对象里面的关联的值
  2. 参数二:const void *key : 关联使用的key值,通过这个key取出对应的值

关联使用的策略:

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
策略 对应 @property 描述
OBJC_ASSOCIATION_ASSIGN (assign)或者(unsafe_unretained) 指定一个关联对象的弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC (nonatomic, strong) 不能原子化的强引用
OBJC_ASSOCIATION_COPY_NONATOMIC (nonatomic, copy) copy引用,不能原子化
OBJC_ASSOCIATION_RETAIN (atomic, strong) 原子化的强引用
OBJC_ASSOCIATION_COPY (atomic, copy) 原子化的copy引用

2. 关联对象的应用

举个例子,说了半天关联对象可以为分类添加属性,那么我们就把这个例子写一下。

@interface CTObject (Category)

@property (nonatomic, copy) NSString *cate_p1;

@end
@implementation CTObject (Category)
- (void)setCate_p1:(NSString *)cate_p1{
    
    objc_setAssociatedObject(self, @"cate_p1",cate_p1, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

3. 关联对象的底层原理

上面两节对关联对象做了简单的介绍和其使用的举例,下面我们来研究一下它的底层实现。

3.1 objc_setAssociatedObject

我们先看看objc_setAssociatedObject的源码,由于使用了各种C++的语法和嵌套,嵌套过程就不多说了,以下是嵌套的代码:

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

static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
  _object_set_associative_reference(object, key, value, policy);
}

static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};

由以上代码我们可以知道objc_setAssociatedObject实际调用的是_object_set_associative_reference函数,下面我们就来到_object_set_associative_reference看看它究竟是如何实现的。

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

acquireValue 源码:

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

3.2 objc_getAssociatedObject

objc_getAssociatedObject就没有那么多嵌套了,直接就可以看出是调用的_object_get_associative_reference函数。

objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

_object_get_associative_reference 源码:

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

_object_get_associative_reference的实现也很简单:

3.3 objc_removeAssociatedObjects

对于关联对象其实还有一个函数objc_removeAssociatedObjects,只不过我们基本不用他,根据名字我们就可以知道该函数是移除关联对象的。这里也嵌套了一层代码,最终调用的是_object_remove_assocations

objc_removeAssociatedObjects 源码:

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}

_object_remove_assocations 源码:

// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
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();
    }
}

根据注释我们可以知道_object_remove_assocations函数是会对性能有影响的。

4. 总结

至此我们的关联对象就基本分析完毕了,但是由于本人才疏学浅,有些地方用词不当,一些C++语法不是很熟悉,有些表述不完整,不贴切,但是我也想不出什么好词的,可能也会有些不准确。如有问题欢迎指正。

5. 参考资料

**Apple Associative References
**

上一篇 下一篇

猜你喜欢

热点阅读