作为OC扩展机制的两个重要特性,使用Category来扩展方法,使用Associated Object来扩展属性,二者完美地扩展了OC的扩展机制。上文我们分析了Category的底层原理,本文我们对Associated Object的底层原理进行分析。
源码基于objc4-750,下载地址:objc4-750。
通过分析runtime源码发现,关联属性并未定义到category_t中,而是定义到AssociationsManager来进行管理的,包括关联属性的添加、获取、移除等操作。
定义AssociationsHashMap来对关联列表进行具体操作。AssociationsManager通过输入的对象作为地址,取出对象所对应的关联列表,然后通过key值取出关联列表中的关联属性ObjcAssociationMap,其中ObjectAssociationMap又定义了关联策略和关联值。这样就构成联对象的基本框架结构。
下面我们分别对_object_set_associative_reference、_object_get_associative_reference、_object_remove_assocations的源码进行解读。
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
//移除旧的关联属性,传nil替换
ObjcAssociation old_association(0, nil);
//进行内存管理!!!
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
//初始化 HashMap,以对象为单位
AssociationsHashMap &associations(manager.associations());
//取出要关联属性对象的地址,当前对象的地址按位取反(key)
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;
//ObjcAssociation
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
//key对象的hashMap不存在,则创建hashMap:ObjectAssociationMap
//将要关联的属性和策略封装到ObjcAssociation,并根据key添加到这个nashMap中
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.
//关联的属性为空,取出之前的关联值,并移除,相当于removeObjectForKey
//同样是根据对象内存地址disguised_object找到它的关联属性列表,再根据key值取出ObjectAssociationMap,最后移除
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);
}
AssociationsManager类:
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
主要管理AssociationsHashMap对象,同时在运行构造函数时执行加锁,运行析构函数时执行解锁。防止多线程访问时,对同一个_map进行多次访问,确保了_map变量访问的安全性。
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
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()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.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操作思想及逻辑与_object_set_associative_reference类似。
关联对象什么时候释放? 是在dealloc时随着原对象释放而释放的。
首先_object_set_associative_reference函数要执行object->setHasAssociatedObjects():将关联对象与当前的类设置关联关系。
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
该函数中设置isa的指针,设置标识has_assoc,该标识用于在对象销毁时,识别并销毁关联对象。
Dealloc方法的调用顺序是从子类到父类,直至NSObject。NSObject运行的dealloc时,会进入其根函数rootDealloc:
- (void)dealloc {
_objc_rootDealloc(self);
}
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
此处正是对包含isa.has_assoc的判断来调用object_dispose:
id object_dispose(id obj)
{
if (!obj) return nil;
//销毁对象
objc_destructInstance(obj);
//释放内存
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//析构c++
if (cxx) object_cxxDestruct(obj);
//移除关联对象
if (assoc) _object_remove_assocations(obj);
//ARC模式下,调用实例变量的release,移除weak引用
obj->clearDeallocating();
}
return obj;
}
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;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
通过上述代码的分析,最终调用ReleaseValue来最终释放关联属性。这与我们的结论不谋而合,关联对象的释放是在dealloc时随着原对象释放而释放的。
总结:
简单来说关联属性的操作流程就是通过全局hashMap,根据关联对象查找对应的子hashMap,然后根据key值查找对应的属性值和管理策略。
比如对于一个控件,需要设置点击事件UITapGestureRecongnizer,并响应相关的操作。但是点击事件只能传递int类型的tag信息,无法携带NSString数据。那么如何实现NSString数据在UITapGestureRecongnizer中传递呢?使用Category,并在Category中添加关联属性。
(1)定义分类 UITapGestureRecognizer+AssociatedObject.h
#import
@interface UITapGestureRecognizer (AssociatedObject)
@property (nonatomic, strong) NSString * str;
@end
(2)实现str属性的setter和getter方法
#import "UITapGestureRecognizer+AssociatedObject.h"
#import
static char * StrDataKey = "StrDataKey";
@implementation UITapGestureRecognizer (AssociatedObject)
- (void)setStr:(NSString *)str {
//设置关联属性
objc_setAssociatedObject(self, StrDataKey, str, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)str {
//获取管理属性
return objc_getAssociatedObject(self, StrDataKey);
}
@end
(3)在ViewConyroller中使用:
#import "UITapGestureRecognizer+AssociatedObject.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer * tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
tapGes.str = @"123456";
[self.testView addGestureRecognizer:tapGes];
}
- (void) tapAction:(UITapGestureRecognizer *)sender {
UITapGestureRecognizer * tap = (UITapGestureRecognizer *)sender;
NSLog(@"tap.str:%@", tap.str);
}
@end
这样便实现了NSString数据在UITapGesturereReconizer事件中的传递,并进行下一步操作。其它类型的数据传递与此类似,修改关联属性的类型即可。