iOS中的关联对象

在iOS开发中,我们经常使用到category,使用category可以给类添加方法或者属性,在添加属性的时候,如果仅仅声明属性,编译器并不会自动生成set和get方法,此时直接使用属性进行读写会发生crash,此时需要通过关联对象来增加属性的set和get方法实现。

关联对象的使用

先创建一个类名称为Custom,并且增加category命名为Prop,写入代码

Custom.h


#import <Foundation/Foundation.h>


NS_ASSUME_NONNULL_BEGIN

@interface Custom : NSObject

@property(nonatomic, strong) NSString *string;

@end

NS_ASSUME_NONNULL_END

Custom.m

#import "Custom.h"

@implementation Custom

@end

Custom+Prop.h

#import "Custom.h"

NS_ASSUME_NONNULL_BEGIN

@interface Custom (Prop)

@property(nonatomic, strong) NSString *str;

@end

NS_ASSUME_NONNULL_END

Custom+Prop.m

使用关联对象需要加<objc/runtime.h>头文件

objc_setAssociatedObject和objc_getAssociatedObject的第二个参数可以是@selector(propertyName),propertyName为属性名称

OBJC_ASSOCIATION_RETAIN_NONATOMIC是policy参数的取值

#import "Custom+Prop.h"
#import <objc/runtime.h>

@implementation Custom (Prop)

- (void)setStr:(NSString *)str
{
    
    
    objc_setAssociatedObject(self, @selector(str), str, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)str
{
    
    
    return objc_getAssociatedObject(self, @selector(str));
}

@end

代码完成后可以使用Custom的属性str进行读写了。

category为什么可以添加属性和方法

struct category_t {
    
    
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    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;
    }
};

从category的定义中可以看出,category持有了实例方法列表,类方法列表,协议列表,实例属性列表,类属性列表。因此,category可以给类添加相应的实例方法,类方法,协议,实例属性,类属性。

关联对象实现原理

首先了解下关联对象的数据结构

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,所有对象的所有关联对象都保存在这里。

在app的启动阶段,dyld链接动态库的时候,就会调用init初始化函数用来初始化_mapStorage

 using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;

这里的_mapStorage是Map的数据结构,key为DisguisedPtr类型,也就是对象封装后的类型,value是ObjectAssociationMap类型,

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

这里的ObjectAssociationMap也是Map的数据结构,这个Map的key是关联对象的key,也就是objc_getAssociatedObject(id object, const void *key)接口中参数key,ObjectAssociationMap中的value就是关联对象ObjcAssociation。

class ObjcAssociation {
    
    
    uintptr_t _policy;
    id _value;
};

这个是关联对象的部分定义,ObjcAssociation持有了_policy和对应的_value,_value是关联对象的实际取值。

关联对象的数据结构可以总结为:AssociationsManager负责初始化Map类型的_mapStorage,_mapStorage保存了有关联对象的所有实例,一个实例的DisguisedPtr为一个key。_mapStorage的value为当前实例的关联对象,由于关联对象可以有多个,_mapStorage持有了ObjectAssociationMap管理这些关联对象。ObjectAssociationMap的key为代码中设置的key,value为关联对象ObjcAssociation。实际上,关联对象是用两层Map来实现的。

继续使用Custom中的例子,我们对str属性赋值的时候,此时会调用到对应的set方法。

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

set方法会调用到_object_set_associative_reference这个方法

oid
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    
    
  
    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};

    association.acquireValue();
    //判断是否是首次设置关联对象
    bool isFirstAssociation = false;
    {
    
    
        AssociationsManager manager;
        //获取AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        //value不为空
        if (value) {
    
    
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{
    
    });
            if (refs_result.second) {
    
    
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* 新建或者替换 association */
            auto &refs = refs_result.first->second;
            //map中增加key和association
            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);
                    //删除association
                    refs.erase(it);
                    //如果当前实例没有关联对象,AssociationsHashMap中需要删除这个对象
                    if (refs.size() == 0) {
    
    
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    //判断是否是首次设置关联对象,如果是,需要设置实例的标记位
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

在设置关联对象的时候,需要获取到AssociationsHashMap,然后找到对象的ObjectAssociationMap,把ObjcAssociation插入。这里需要注意的是如果value为nil,也就是给关联对象赋值为nil,需要把ObjcAssociation删除,如果当前对象已经没有关联对象,需要把当前对象从AssociationsHashMap中删除。

最后需要释放之前持有的对象。

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

只有强持有的对象才需要release。

在取用关联对象的时候,会调用objc_getAssociatedObject方法

id objc_getAssociatedObject(id object, const void *key)
{
    
    
    return _object_get_associative_reference(object, 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();
}

get方法相对简单些,获取AssociationsHashMap然后获取AssociationsHashMap,查找对应的关联对象值返回。

关联对象的释放

当一个实例对象销毁的时候,dealloc方法内部会判断当前对象有没有关联对象,如果有就会调用_object_remove_associations方法

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

    SmallVector<ObjcAssociation *, 4> laterRefs;

    for (auto &i: refs) {
    
    
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
    
    
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
    
    
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
    
    
        later->releaseHeldValue();
    }
}

找出对应的ObjectAssociationMap,从associations中移除相应的对象。

猜你喜欢

转载自blog.csdn.net/u011608357/article/details/128270844