我们在runtime.h 文件中,有这么一部分代码
/* Associative References */
/**
* 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. */
};
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
这部分就是我们经常用的关联引用部分,给category 增加一个假的成员变量。
基本用法
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@implementation ViewController
static NSString *_strFlag;
-(void)addValue{
objc_setAssociatedObject(self, &_strFlag, @"你好", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(void)getvaule{
NSString * str = objc_getAssociatedObject(self, &_strFlag);
NSLog(@"getValue %@",str);
}
-(void)changeValue{
objc_setAssociatedObject(self, &_strFlag, @"挺好的", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self getvaule];
}
-(void)deleteValue{
objc_setAssociatedObject(self, &_strFlag, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self getvaule];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self addValue];
[self getvaule ];
[self changeValue];
[self deleteValue];
// Do any additional setup after loading the view, typically from a nib.
}
@end
结果
2018-04-18 09:14:04.888137+0800 关联引用[30473:10915294] getValue 你好
2018-04-18 09:14:04.888323+0800 关联引用[30473:10915294] getValue 挺好的
2018-04-18 09:14:04.888412+0800 关联引用[30473:10915294] getValue (null)
使用场景
- 1.为现有的类添加私有变量
- 2.为现有的类添加公有属性
- 3.为KVO创建一个关联的观察者。
API
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
在这API 中有个objc_AssociationPolicy 宏定义,我感觉这个图挺好的。就拿过来了。
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) / @property (unsafe_unretained) | 弱引用关联对象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 强引用关联对象,且为非原子操 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 复制关联对象,且为非原子操作 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 强引用关联对象,且为原子操作 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 复制关联对象,且为原子操作 |
参数的意义:
- 1.id object 设置关联对象的实例对象
- 2.const void *key 区分不同的关联对象的 key。其实这个key是个地址,只要是不变的地址就可以。(地址不变的有静态变量,函数指针等)
举例
使用 &AssociatedObjectKey 作为key值
static char AssociatedObjectKey = "AssociatedKey";
使用AssociatedKey 作为key值
static const void *AssociatedKey = "AssociatedKey";
使用@selector
@selector(associatedKey)
还有很多写法。这里就不做介绍了。
- 3.id value 关联的对象
- 4.objc_AssociationPolicy policy 关联对象的存储策略。对应关系如上图。
源码分析
objc_setAssociatedObject(id object, const void *key, id value,
objc_AssociationPolicy policy)
objc_setAssociatedObject(id object, const void *key, id value,
objc_AssociationPolicy policy)
{
objc_setAssociatedObject_non_gc(object, key, value, policy);
}
调用下面函数
void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
接着调用下面函数
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);
}
这个函数才是真正的实现
知识补充
ObjcAssociation old_association(0, nil); 相当于
ObjcAssociation * old_association;
ObjcAssociation._policy=0;
ObjcAssociation._value=nil;
AssociationsHashMap &associations(manager.associations());相当于
AssociationsHashMap & mm = manager.associations();
AssociationsHashMap &associations = mm;
流程图
1.从流程图中,我们很容易能总结出来,关联引用的所有数据都是保存在一个全局map
2.全局map 的key 是object 转换成的数值,value 是一个newMap
3.newMap 的key 就是我们传入的key 。值是ObjcAssociation 对象。
4.ObjcAssociation 对象保存policy 和value 值。
结构图如下
5 从图中能看出来,我们要是传入的值是nil, 就相当于把值清空。
6 这里注意acquireValue(value, policy)。会根据外界传入的策略进行消息转发retain 或者copy
从这里有句话。
object->setHasAssociatedObjects();
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.indexed) return;
if (newisa.has_assoc) return;
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
从这里我们能看出来,objc-class 类中的 has_assoc 标志位是关联引用标志位,有关联引用,就标志位true。
id objc_getAssociatedObject(id object, const void *key)
id
objc_getAssociatedObject(id object, const void *key)
{
return objc_getAssociatedObject_non_gc(object, key);
}
id objc_getAssociatedObject_non_gc(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
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) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
1.我们知道了关联引用的存储结构后,这里就很简单的看懂了。先获取全局的map
2.根据object 转换的数字,从map中招NewMap.没有找到就返回
3.从NewMap中招key 对应的ObjcAssociation 对象,没有找到就返回
4.找到后检查缓存策略是不是OBJC_ASSOCIATION_GETTER_RETAIN,是就发送消息retain 该对象
5 在获取到值的时候并且policy 是OBJC_ASSOCIATION_GETTER_AUTORELEASE 的时候,发送消息SEL_autorelease
void objc_removeAssociatedObjects(id object)
void objc_removeAssociatedObjects(id object)
{
#if SUPPORT_GC
if (UseGC) {
auto_zone_erase_associative_refs(gc_zone, object);
} else
#endif
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
}
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());
}
1.这里我们能看出来。我们首先先检查对象有没有关联引用。没有就返回了。
2.这里我们先声明一个变量 vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
用来存放即将被删除的对象。
3.加入对象有关联引用。获取全局map,要是全局map没有值,那么也返回了。(不知道为什么这里没有清除标志位)
4.找到全局map中object 对应的NewMap.。
5.检查newMap中的数据,将数据保存到一个elements模板。
6.删除NewMap。
7.从elements模板中获取ObjcAssociation 对象,根据策略将对象释放。