iOS小集

关联对象实现原理

2020-05-11  本文已影响0人  CoderJRHuo

问题:

Category能否添加成员变量?如果可以,如何给Category添加成员变量?

1. 给Category添加属性

在之前我们通过对Category底层源码的解析知道了:Category是不可以添加成员变量的,因为底层的结构体没有存储成员变量列表,但是存储了属性的列表,所以一个Category是可以添加属性的,但是这个属性不会生成成员变量和set get方法实现,只会生成set get方法的声明,需要自己实现set get方法。

使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系。我们通过之前的学习知道,对象一开始初始化的时候其属性为nil,给属性赋值其实就是让属性指向一块存储内容的内存,使这个对象的属性跟这块内存产生一种关联。

那么如果想动态的添加属性,其实就是动态的产生某种关联就好了。而想要给系统的类添加属性,只能通过分类。

赋值是通过set方法给成员变量赋值,取值是通过get方法得到成员变量的值:

-(void)setAge:(int)age
{
    _age = age;
}
-(int)age
{
    return _age;
}

这里给NSObject添加name属性,创建NSObject的分类

@property (nonatomic, strong) NSString *name;

通过Category的本质我们知道,虽然在分类中可以写@property
添加属性,但是不会自动生成私有属性(成员变量),也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。

1.1 方法一:使用静态全局变量给分类添加属性

static NSString *_name;

@implementation NSObject (Test)

-(void)setName:(NSString *)name
{
    _name = name;
}
-(NSString *)name
{
    return _name;
}

但是这样_name静态全局变量与类并没有关联,无论对象创建与销毁,只要程序在运行_name变量就存在,而且创建多个对象的时候,每个对象的name不是独立的。应为是全局的变量, 改变一个对象的name, 其他对象的name也会被更改, 并不是真正意义上的属性。

1.2 方法二:使用全局字典来给分类添加属性

既然上面的方法对象之前的属性不是独立的,那么我们可以使用一个字典来分别存储对象的属性的值:

NSMutableDictionary *names_;

@implementation NSObject (Test)

+(void)load {
    names_ = [NSMutableDictionary dictionary];
}

-(void)setName:(NSString *)name
{
    NSString *key = [NSString stringWithFormat:@"%p", self];
    names_[key] = name;
}
-(NSString *)name
{
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return names_[key];
}

一个对象的成员变量是存放在这个对象内部的,但是这种方法和对象没有关系的,因为属性的值是存放在一个全局字典内部的,假如我们还需要给分类添加多个属性,那么又需要添加一个新的全局字典,所以这样的方式也存在问题。

所以想要给一个分类添加属性,应该使用关联对象技术来实现。

2. 使用RunTime关联对象动态添加属性(间接添加成员变量)

RunTime提供了动态添加属性和获得属性的方法,可以使得属性和对象产生关联。

虽然可以间接的添加成员变量,但是和原类的成员变量完全是两码事,原类的成员变量就是在一个实例对象内部,而使用关联对象只是实现了分类属性的set get方法,使分类的属性可以像成员变量那样使用。

2.1 动态添加属性

/** 
 * 参数1 id object : 给哪个对象添加属性,这里要给自己添加属性,用self
 * 参数2 void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
 * 参数3 id value : 关联的值,也就是set方法传入的值给属性去保存。
 * 参数4 objc_AssociationPolicy policy : 策略,属性以什么形式保存。
 */
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

// objc_AssociationPolicy policy
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性   
};

key值只要是一个指针即可,保证唯一性即可,可以使用的方法:

static const char NSObjectName;
objc_setAssociatedObject(self, &NSObjectName, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, _cmd);
// _cmd就是一个自己的方法实现,在get方法内部相当于@selector(name)

2.2 获得属性

/** 
 * 参数1 id object : 获取哪个对象里面的关联的属性。
 * 参数2 void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
 */
objc_getAssociatedObject(id object, const void *key);

2.3 移除所有关联对象

// 移除所有关联对象
objc_removeAssociatedObjects(self);

此时已经成功给NSObject添加name属性,并且NSObject对象可以通过点语法为属性赋值:

NSObject *objc = [[NSObject alloc]init];
objc.name = @"qq";
NSLog(@"%@",objc.name);

NSObject *objc1 = [[NSObject alloc]init];
objc1.name = @"nn";
NSLog(@"%@",objc1.name);
    
NSLog(@"%@",objc.name);
2020-04-29 21:04:38.417962+0800 关联对象实现原理[45634:18454204] qq
2020-04-29 21:04:38.418076+0800 关联对象实现原理[45634:18454204] nn
2020-04-29 21:04:38.418187+0800 关联对象实现原理[45634:18454204] qq

3 关联对象的原理

实现关联对象技术的底层核心对象有:

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation

其中Map同我们平时使用的字典类似。通过key-value一一对应存值。

通过源码来探寻这些对象的存在形式以及其作用:

设置函数objc_setAssociatedObject函数

来到runtime源码,首先找到objc_setAssociatedObject函数,看一下其实现

objc源码路径:runtime/objc-runtime.mm

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

↓↓↓

_object_set_associative_reference函数

objc源码路径:runtime/objc-references.mm

void _object_set_associative_reference(id object, 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;
    
    assert(object);
    
    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));
    
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // ---------- 核心对象1 ---------- **********
        AssociationsManager manager;
        
        // ---------- 核心对象2 AssociationsHashMap ----------
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    
                    // ---------- 核心对象3 ObjcAssociation ----------
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                
                // ---------- 核心对象4 ObjectAssociationMap ----------
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

_object_set_associative_reference函数内部我们可以全部找到我们上面说过的实现关联对象技术的核心对象。接下来我们来一个一个看其内部实现原理探寻他们之间的关系。

3.1 核心对象之间的关联

通过AssociationsManager内部源码发现,AssociationsManager内部有一个AssociationsHashMap对象。

class AssociationsManager {
    // 内部有一个AssociationsHashMap对象
    static AssociationsHashMap *_map;
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

↓↓↓

我们来看一下AssociationsHashMap内部的源码。

#if TARGET_OS_WIN32
    typedef hash_map<void *, ObjcAssociation> ObjectAssociationMap;
    typedef hash_map<disguised_ptr_t, ObjectAssociationMap *> AssociationsHashMap;
#else
    typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
    
    // ---------- ObjectAssociationMap 与 ObjcAssociation ----------
    class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    // --------------------------------------------------
    
    typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
    
    // ---------- AssociationsHashMap 与 ObjectAssociationMap
    class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    // --------------------------------------------------
#endif

通过AssociationsHashMap内部源码我们发现AssociationsHashMap继承自unordered_map首先来看一下unordered_map内的源码:

unordered_map

unordered_map源码中我们可以看出_Key_Tp也就是前两个参数对应着map中的KeyValue,那么对照上面AssociationsHashMap内源码发现_Key中传入的是disguised_ptr_t_Tp中传入的值则为ObjectAssociationMap*

紧接着我们来到ObjectAssociationMap中,上图中ObjectAssociationMap已经标记出,我们发现ObjectAssociationMap中同样以key、Value的方式存储着ObjcAssociation

↓↓↓

接着我们来到ObjcAssociation

class ObjcAssociation {
        uintptr_t _policy; // 策略
        id _value; // 值
    public:
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        ObjcAssociation() : _policy(0), _value(nil) {}

        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };

我们发现ObjcAssociation存储着_policy_value,而这两个值我们可以发现正是我们调用objc_setAssociatedObject函数传入的值,也就是说我们在调用objc_setAssociatedObject函数中传入的valuepolicy这两个值最终是存储在ObjcAssociation中的。

现在我们已经对AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四个对象之间的关系有了简单的认识,那么接下来我们来细读源码,看一下objc_setAssociatedObject函数中传入的四个参数分别放在哪个对象中充当什么作用。

重新回到_object_set_associative_reference函数实现中:
void _object_set_associative_reference(id object, 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;
    
    assert(object);
    
    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));
    
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    
    // 一. 首先根据我们传入的value经过acquireValue函数处理获取new_value
    // new_value是根据不同的策略返回不同的值
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 二. 之后创建AssociationsManager manager,以及拿到manager内部的AssociationsHashMap即associations。
        // associations是字典,里面存储形式是key-value
        // key是object经过转化的disguised_ptr_t类型
        // value是另外一个字典ObjectAssociationMap类型,其存储的key是传入的属性,value是一个ObjcAssociation类型
        // ObjcAssociation存储了当前的值和策略
        
        // ---------- 核心对象1 ---------- 
        AssociationsManager manager;
        
        // ---------- 核心对象2 AssociationsHashMap ----------
        AssociationsHashMap &associations(manager.associations());
        
        // object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object
        // DISGUISE函数内部仅仅进行了位运算
        disguised_ptr_t disguised_object = DISGUISE(object);
        
        // 成功获取到转化之后的值
        if (new_value) {
            // 根据转化之后的obj从AssociationsHashMap对象中找到ObjectAssociationMap
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    
                    // ---------- 核心对象3 ObjcAssociation ----------
                    // ObjcAssociation对象里面存储值和关联策略
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                
                // ---------- 核心对象4 ObjectAssociationMap ----------
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

细读上述源码我们可以发现,首先根据我们传入的value经过acquireValue函数处理获取new_valueacquireValue函数内部其实是通过对策略的判断返回不同的值

static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

之后创建AssociationsManager manager,以及拿到manager内部的AssociationsHashMapassociations

之后我们看到了我们传入的第一个参数object
object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object

typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }

DISGUISE函数其实仅仅对object做了位运算

之后我们看到被处理成new_valuevalue,同policy被存入了ObjcAssociation中。
ObjcAssociation对应我们传入的key被存入了ObjectAssociationMap中。
disguised_objectObjectAssociationMap则以key-value的形式对应存储在associations中也就是AssociationsHashMap中。

// ---------- 核心对象4 ObjectAssociationMap ----------
ObjectAssociationMap *refs = new ObjectAssociationMap;
ssociations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();

如果我们value设置为nil的话那么会执行下面的代码

// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i !=  associations.end()) {
    ObjectAssociationMap *refs = i->second;
    ObjectAssociationMap::iterator j = refs->find(key);
    if (j != refs->end()) {
        old_association = j->second;
        refs->erase(j);
    }
}

从上述代码中可以看出,如果我们设置valuenil时,就会将关联对象从ObjectAssociationMap中移除。

最后我们通过一张图可以很清晰的理清楚其中的关系

associationObj_handler

通过上图我们可以总结为:一个实例对象object就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociationObjcAssociation中存储着关联对象的valuepolicy策略。

由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。

取值objc_getAssociatedObject函数

objc_getAssociatedObject内部调用的是_object_get_associative_reference

↓↓↓

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        // 同样的有AssociationsManager
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
       
        // 在哈希表中查找disguised_object
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            
            // 如果存在,取出ObjectAssociationMap
            // 并在ObjectAssociationMap中查找属性key对应的valued,即ObjcAssociation对象
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                
                // 将value和policy取出
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}

_object_get_associative_reference函数内部可以看出,像set方法中那样,反向将value一层一层取出最后return出去。

移除objc_removeAssociatedObjects函数

objc_removeAssociatedObjects用来删除所有的关联对象,objc_removeAssociatedObjects函数内部调用的是_object_remove_assocations函数

objc源码路径:runtime/objc-runtime.mm

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

↓↓↓

objc源码路径:runtime/objc-references.mm

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            
            // 遍历所有的ObjectAssociationMap
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // 删除操作
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

上述源码可以看出_object_remove_assocations函数将object对象向对应的所有关联对象全部删除。

4 总结

关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中,如果设置关联对象为nil,就相当于是移除关联对象。

此时我们我们在回过头来看objc_AssociationPolicy policy参数: 属性以什么形式保存的策略。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性   
};

我们会发现其中只有RETAINCOPY而为什么没有weak呢?

通过上面对源码的分析我们知道,object经过DISGUISE函数位运算被转化为了disguised_ptr_t类型的disguised_object

disguised_ptr_t disguised_object = DISGUISE(object);

而同时我们知道,weak修饰的属性,当没有拥有对象之后就会被销毁,并且指针置为nil,那么在对象销毁之后,虽然在map中存在值object对应的AssociationsHashMap,但是因为object地址已经被置位nil,会造成坏地址访问而无法根据object对象的地址转化为disguised_object了。

上一篇下一篇

猜你喜欢

热点阅读