1.使用场景
1.添加私有属性,用于更好地实现细节(声明在实现文件中)
2.添加公有属性,增强category的功能(声明在头文件中)
3.创建一个用于KVO的关联观察者
例:
#import "DKObject+Category.h"
#import <objc/runtime.h>
@implementation DKObject (Category)
- (NSString *)categoryProperty {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setCategoryProperty:(NSString *)categoryProperty {
objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
分析:
- runtime源码runtime.h中可以找到相关的函数定义:
//1.添加关联对象,传入nil时,相当于清空该关联对象的值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
//2.获取关联对象
id objc_getAssociatedObject(id object, const void *key);
//3.移除所有关联对象,一般不使用,这样会把所有的关联对象都移除。
void objc_removeAssociatedObjects(id object);
key值的获取:key值必须是一个对象级别的唯一常量。通常有三种写法:
1)声明static char kAssociatedObejctKey;使用&kAssociatedObejctKey作为Key
2)声明static void *kAssociatedObejctKey = &kAssociatedObejctKey;使用kAssociatedObejctKey 作为Key
3)使用selector,用getter方法作为Key。例如@selector(函数名称),更简单就是使用_cmd(代表当前方法的选择子)。这样可以去为Key命名的烦恼,推荐!关联策略:
OBJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
OBJC_ASSOCIATION_COPY
2.实现原理
//runtime中的源代码,省略了具体实现:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
ObjectAssociationMap *refs = i->second;
...
}
if (old_association.hasValue()) ReleaseValue()(old_association);
}
分析:
- AssociationsManager
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map;
public:
AssociationsManager() { _lock.lock(); }
~AssociationsManager() { _lock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
spinlock_t AssociationsManager::_lock;
AssociationsHashMap *AssociationsManager::_map = NULL;
它维护了 spinlock_t 和 AssociationsHashMap 的单例,初始化它的时候会调用 lock.lock() 方法,在析构时会调用 lock.unlock(),而 associations 方法用于取得一个全局的 AssociationsHashMap 单例.也就是说 AssociationsManager 通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作。
- AssociationsHashMap
- ObjcAssociationMap
ObjectAssociationMap 则保存了从 key 到关联对象 ObjcAssociation 的映射,这个数据结构保存了当前对象对应的所有关联对象
- 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 对象
- 关联对象由 AssociationsManager 管理并在 AssociationsHashMap 存储
- 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap 中
- ObjectAssociationMap 则是用于存储关联对象的数据结构
- 每一个对象都有一个标记位 has_assoc 指示对象是否含有关联对象
参考资料
1.http://nshipster.cn/associated-objects/
2.http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/
3.https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%20AssociatedObject%20%E5%AE%8C%E5%85%A8%E8%A7%A3%E6%9E%90.md