简介
AssociateObject 一般用于对已有对象的参数扩展,可认为是一种比较偷懒的继承方式,搭配Category使用,基本可以等同于继承了
Category不支持ivar主要还是因为类在被注册之后就不可再对其变量进行增减,免得造成同一个类,功能意义却大不相同的情况。
AssociateObject采用了一套和类完全无关的管理方式,用于和类的实例彻底解耦,所以其内存管理,释放时机和实例都有所不同,以下稍作总结。
setter/getter
- objc_getAssociatedObject(self, key);
getter非常简单,从全局map中以预先设置的key拿到对应对象。此处的key一般可写做_cmd,也就是当前getter方法,之前还需要写一个static char类型的key,使用时候还需要转换为指针,后来发现直接使用_cmd更好,还能保证key的唯一性。 - objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
setter的value需要是对象类型,基本类型需要使用字面量包装下,最后一个修饰符稍后再讲,先看下关联对象被储存在什么地方,并且是如何储存的。
存取原理
先介绍四个关键类
- AssociatedManager
- AssociatedHashMap
- ObjcAssociationMap
- ObjcAssociation
从下往上依次介绍,顺便推理原理
- ObjcAssociation,对应和实例绑定的实际对象,内部为对象具体信息,值、类型、修饰符等等。
- ObjcAssociationMap,该map用于储存查找上面实际对象,此处的key猜测应该是上述两个存取方法中的key(_cmd),也很好理解
- AssociatedHashMap,该层的意义就是为全局的关联对象操作做准备了,系统下各个类都可以创建关联对象,所以建一个存放类和其对应的所有关联属性的容器就很有必要了,此处map的value为所有关联对象的数组,要注意的是key是什么?是当前类呢还是类的实例?(其实细想下就会知道,给UIView增加left属性,那么每个view对象的left都是不一样的,所以key应该为当前实例,也就是self)。
- 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。
由此可见,传言中的关联对象释放时机比实例晚的多,可以在这句话中得到验证。
网友评论