美文网首页iOS技术
iOS之AssociateObject(关联对象)

iOS之AssociateObject(关联对象)

作者: 环宇飞杨 | 来源:发表于2020-05-07 13:12 被阅读0次

    简介

    AssociateObject 一般用于对已有对象的参数扩展,可认为是一种比较偷懒的继承方式,搭配Category使用,基本可以等同于继承了

    Category不支持ivar主要还是因为类在被注册之后就不可再对其变量进行增减,免得造成同一个类,功能意义却大不相同的情况。

    AssociateObject采用了一套和类完全无关的管理方式,用于和类的实例彻底解耦,所以其内存管理,释放时机和实例都有所不同,以下稍作总结。

    setter/getter

    1. objc_getAssociatedObject(self, key);
      getter非常简单,从全局map中以预先设置的key拿到对应对象。此处的key一般可写做_cmd,也就是当前getter方法,之前还需要写一个static char类型的key,使用时候还需要转换为指针,后来发现直接使用_cmd更好,还能保证key的唯一性。
    2. objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      setter的value需要是对象类型,基本类型需要使用字面量包装下,最后一个修饰符稍后再讲,先看下关联对象被储存在什么地方,并且是如何储存的。

    存取原理

    先介绍四个关键类

    • AssociatedManager
    • AssociatedHashMap
    • ObjcAssociationMap
    • ObjcAssociation
      从下往上依次介绍,顺便推理原理
    1. ObjcAssociation,对应和实例绑定的实际对象,内部为对象具体信息,值、类型、修饰符等等。
    2. ObjcAssociationMap,该map用于储存查找上面实际对象,此处的key猜测应该是上述两个存取方法中的key(_cmd),也很好理解
    3. AssociatedHashMap,该层的意义就是为全局的关联对象操作做准备了,系统下各个类都可以创建关联对象,所以建一个存放类和其对应的所有关联属性的容器就很有必要了,此处map的value为所有关联对象的数组,要注意的是key是什么?是当前类呢还是类的实例?(其实细想下就会知道,给UIView增加left属性,那么每个view对象的left都是不一样的,所以key应该为当前实例,也就是self)。
    4. AssociatedManager,全局单例无疑了,用于runtime下便捷调用AssociatedHashMap容器的存取方法唯一入口,另外还控制着AssociatedHashMap的生命周期。

    下面摘抄下源码:

    void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
        // retain the new value (if any) outside the lock.
        ObjcAssociation old_association(0, nil);
        id new_value = value ? acquireValue(value, policy) : nil;
        {
            AssociationsManager manager;
            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;
                        j->second = ObjcAssociation(policy, new_value);
                    } else {
                        (*refs)[key] = ObjcAssociation(policy, new_value);
                    }
                } else {
                    // create the new association (first time).
                    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);
    }
    
    类图

    内存管理

    objc_setAssociatedObject方法接收的四个参数中,最后一个与引用计数相关

    /**
     * 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. */
    };
    

    基本和MRC下的属性修饰符无异,对照使用即可,一个注意点就是assign修饰下对象野指针的问题,一些面试题中会问如何实现weak类型的关联对象,就是需要使用OBJC_ASSOCIATION_ASSIGN,仿照weak原理,当对象释放时,将关联对象置空即可。那么关联对象是什么时候释放的呢?

    释放时机

    dealloc 方法的调用顺序是从子类到父类直至 NSObject 的,NSObject 的 dealloc 会调用 object_dispose() 函数,进而移除 Associated Object。

    由此可见,传言中的关联对象释放时机比实例晚的多,可以在这句话中得到验证。

    相关文章

      网友评论

        本文标题:iOS之AssociateObject(关联对象)

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