iOS技术

iOS之AssociateObject(关联对象)

2020-05-07  本文已影响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需要是对象类型,基本类型需要使用字面量包装下,最后一个修饰符稍后再讲,先看下关联对象被储存在什么地方,并且是如何储存的。

存取原理

先介绍四个关键类

  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。

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

上一篇 下一篇

猜你喜欢

热点阅读