AssociationedObject多用于在Category中为特定类扩展成员变量,也有用于在运行时为某些对象动态建立成员变量。AssociationedObject能够说是一种特殊的成员变量。 这篇文章是来详细解释AssociationedObject的实现原理,篇幅较长。算法
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
复制代码
这是在调用objc_setAssociatedObject
时须要用到的参数,用于指定关联参数的引用策略。安全
OBJC_ASSOCIATION_ASSIGNbash
将关联引用描述为弱引用数据结构
OBJC_ASSOCIATION_RETAIN_NONATOMICapp
将关联引用描述为强引用,而且非原子性。less
OBJC_ASSOCIATION_COPY_NONATOMICide
将关联引用描述为拷贝引用,而且非原子性。函数
OBJC_ASSOCIATION_RETAINui
将关联引用描述为强引用,而且为原子性。this
OBJC_ASSOCIATION_COPY
将关联引用表述为拷贝引用,而且为原子性。
这一段比较好理解,与Property同样,关联引用也能够设置引用描述。
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
复制代码
建立和设置一个关联对象的方法。
将一个选定值(value)经过Key-Value的形式挂载在目标对象(object)上,同时指定关联的策略(policy),这样就能生成一个关联对象。
经过将目标对象(object)上指定的Key对应的值(value)设置nil,便可以将已存在的关联对象清除。
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
复制代码
关联对象值的获取方法。
经过关联Key,在目标对象(object)中,获取到关联对象的值(返回值)。
/** * 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 object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
复制代码
移除目标对象(objct)的全部关联对象。
官方在这作了提示:这个方法的主要目的是为了让对象更容易返回到原始状态,调用此方法会将绑定在这个目标对象上的全部关联对象都清除。若是别的开发也为这个object设置了关联对象,或者你在别的模块中也为其设置了不一样的关联对象,调用此方法后会被一并删除。一般清除关联对象,请经过objc_setAssociatedObject将关联对象设置为nil的方式来清除关联对象。
在底层实现上,分为GC版和无GC版,GC是MacOS的垃圾回收机制,不过如今也被弃用了,推荐使用ARC。而iOS上只有MRC和ARC,所以这里只截取了无GC版的代码。
在这个方法中有不少的数据结构,首先介绍下数据结构,帮助理解,也能够先往下翻看主要流程,有看不明白的再回来查找。
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock, and calling its assocations()
// method lazily allocates the hash table.
spinlock_t AssociationsManagerLock;
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 *AssociationsManager::_map = NULL;
复制代码
AssociationsManager的做用管理lock,当它被建立的时候会加锁,在它被析构的时候会释放锁,同时,有一个全局变量_map,管理的是目标对象与HashMap(Key-Value都为指针)的的关系。
AssociationsManager中有一个全局变量类型为AssociationsHashMap的_map引用。
提供了一个建立AssociationsHashMap的左值引用方法,_map是引用,*_map则是解引用又变成了AssociationsHashMap对象,函数前加了&(取地址符),返回的是AssociationsHashMap对象引用地址。
同时声明了一个类型为AssociationsHashMap的全局变量引用。
typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
// unordered_map的泛型定义
template <class _Key,
class _Tp,
class _Hash = hash<_Key>,
class _Pred = equal_to<_Key>,
class _Alloc = allocator<pair<const _Key, _Tp> > >
复制代码
AssociationsHashMap
继承自unordered_map <disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>
解释下这个泛型的意思
它实现了建立和删除的功能。
typedef unsigned long uintptr_t;
typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
复制代码
这里能够看出disguised_ptr_t
其实是一个unsigned long,它的长度与指针相同,因此被当作指针使用。 ~uintptr_t(value)
:value自己也是个对象指针,将它包装成unsigned long类型,载逐位取反后返回。 id(~dptr)
:将disguised_ptr_t逐位取反后,返回对象(id)指针。
typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
// std::map的泛型定义
template <class _Key,
class _Tp,
class _Compare = less<_Key>,
class _Allocator = allocator<pair<const _Key, _Tp> > >
复制代码
与AssociationsHashMap相似,ObjectAssociationMap
继承自public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>
泛型的意思是:
_key(key_type)对应的是void *,即无类型指针,表示内存地址。
_Tp(mapped_type)对应[ObjcAssociation](#### ObjcAssociation),这个上面提到过,用来存放关联对象的值与关联策略。
_Compare(key_compare)对应的是ObjectPointerLess,提供比较算法(比对指针地址)。
_Allocator(allocator_type)对应的是ObjcAllocator<std::pair<void * const, ObjcAssociation> >,用std::pair实现Key是void * const(内存地址),Value是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; }
};
复制代码
这是关联对象的结构,存储着关联策略_policy
和关联对象的值_value
。
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
复制代码
判断引用策略,若是为Retian的,则会调用objc_retain
将value的引用计数加一,若是是Copy,则会调用Value的copy方法生成新的拷贝,其余的策略不做处理。
inline void
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
标志位设置为true。
这里涉及到了Tagged Pointer的概念,Tagged Pointer是一种为了节约内存占用的策略,它的原理是将指针对象的数据直接存于指针中,在64位系统中被引入。
好比,在64位系统中,一个指针为8字节,当指针关联的值小于8位的时候,系统会将指针转化成Tagged Pointer,并在最后一个bit位加入TaggedPoint标识,这个指针的结构就变成了“0x存储数据+TaggedPoint标识”的结构。
0xb000000000000032
|---------------|-|
0x|----存储数据----|标识|
复制代码
这么作的好处是,减小了内存的占用,又由于数据不须要放入堆中,因此不须要malloc和free,因此读取和运行速度都获得了提高。
这个时候指针已经变成了一个值,而再也不是一个地址,因此它也没有isa指针,因此要先作判断。
struct ReleaseValue {
void operator() (ObjcAssociation &association) {
releaseValue(association.value(), association.policy());
}
};
static void releaseValue(id value, uintptr_t policy) {
if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
return objc_release(value);
}
}
复制代码
这里是关联对象释放的相关代码,若是引用策略为Retian的话,释放时,会将引用计数减一。
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// 建立一个 ObjcAssociation对象,初始化policy为OBJC_ASSOCIATION_ASSIGN,value为nil。
ObjcAssociation old_association(0, nil);
// 根据引用策略处理value。若是Copy,调用value的copy方法获取new_value,若是是Retain的,则持有value,value的引用计数加一,其余策略不做处理。
id new_value = value ? acquireValue(value, policy) : nil;
{
// 建立一个AssociationsManager,在初始化时会加锁,在析构时会解锁。
// C++会默认为对象进行初始化,即已经加锁。
AssociationsManager manager;
// 获取AssociationsManager中全局变量AssociationsHashMap的引用,
// 调用AssociationsHashMap的拷贝构造函数
// 用上面的引用直接初始化新的associations变量。
AssociationsHashMap &associations(manager.associations());
// 建立一个disguised_ptr_t,调用DISGUISE(object),使用目标对象初始化。
disguised_ptr_t disguised_object = DISGUISE(object);
// 若是new_value 不为nil,情景是设置新的关联值、或者修改关联值。
if (new_value) {
// 在AssociationsHashMap中经过迭代器查找,
// 与disguised_ptr_t相对应的ObjectAssociationMap*(i)
// 注:map的结构为std::pair<const disguised_ptr_t, ObjectAssociationMap*>。
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是查找到Map中对应的ObjectAssociationMap*(i),即修改关联值的状况。
// 注:在C++的map中,若是未查找到元素迭代器会返回map.end();
if (i != associations.end()) {
// 将ObjectAssociationMap*(refs)
// 指向AssociationsHashMap(i)的second,
// 这是Map的Value。
ObjectAssociationMap *refs = i->second;
// 在ObjectAssociationMap*(refs)中经过key,
// 来查找对应存在的ObjectAssociationMap对象(j)。
ObjectAssociationMap::iterator j = refs->find(key);
// 若是查找到了对应的ObjcAssociation(j)。
// 注:map的结构为std::pair<void * const, ObjcAssociation>
if (j != refs->end()) {
// 将ObjectAssociationMap的value,
// 即ObjcAssociation,赋值给old_association
old_association = j->second;
// 将这个Map的Value更新为新构建的ObjcAssociation。
j->second = ObjcAssociation(policy, new_value);
} else {
// 若是j是容器惟一的元素,
// 为ObjectAssociationMap的引用(*refs)设置一个Map,
// key为传入的key,Value为新构建的ObjcAssociation。
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// 若是Object是容器里惟一的元素
// 建立一个ObjectAssociationMap
ObjectAssociationMap *refs = new ObjectAssociationMap;
// 在AssociationsHashMap(associations)中,
// 以Key为disguised_object存储。
associations[disguised_object] = refs;
// 为ObjectAssociationMap建立,Key为传入值key,
// value为新构建的ObjcAssociation。
(*refs)[key] = ObjcAssociation(policy, new_value);
// 调用关联目标对象的setHasAssociatedObjects方法。
// 将对象的isa中的has_assoc设置为true
// 即标识这个object有关联对象存在。
// 这个方法位于objc-objct内。
object->setHasAssociatedObjects();
}
} else {
// 当new_value = nil时,
// 经过disguised_object查找对应的ObjectAssociationMap(i)
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是查到了对应的ObjectAssociationMap(i)
if (i != associations.end()) {
// 取出ObjectAssociationMap *refs
ObjectAssociationMap *refs = i->second;
// 经过传入的key查找对应的ObjcAssociation(j)
ObjectAssociationMap::iterator j = refs->find(key);
// 若是查到了对应的ObjcAssociation(j)
if (j != refs->end()) {
// 将旧值取出,赋值给old_association。
old_association = j->second;
// 将ObjcAssociation(j)从ObjectAssociationMap中移除。
refs->erase(j);
}
}
}
//到这里释放锁。
}
// 若是存在旧值,则将旧值释放。
if (old_association.hasValue()) ReleaseValue()(old_association);
}
复制代码
看到这里能够总结一下关联关系的存储结构了。
AssociationsHashMap是管理目标对象(object)与ObjectAssociationMap的关系
ObjectAssociationMap是管理Key与ObjectAssociation(关联对象)的关系。
下面分析的是关于关联对象的取值逻辑,大部分结构体部分在设置值部分都说了,又遇到不理解的回头去看,这里直接将主要实现了。
id _object_get_associative_reference(id object, void *key) {
// 建立一个value。
id value = nil;
// 默认引用策略为assign。
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 建立AssociationsManager并加锁。
AssociationsManager manager;
// 获取全局AssociationsHashMap。
AssociationsHashMap &associations(manager.associations());
// 经过object构建disguised_object。
disguised_ptr_t disguised_object = DISGUISE(object);
// 根据disguised_object查找对应的ObjectAssociationMap。
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是查到了ObjectAssociationMap。
if (i != associations.end()) {
// 提取ObjectAssociationMap。
ObjectAssociationMap *refs = i->second;
// 根据key查找对应的ObjcAssociation。
ObjectAssociationMap::iterator j = refs->find(key);
// 若是ObjcAssociation存在。
if (j != refs->end()) {
// 获取ObjcAssociation
ObjcAssociation &entry = j->second;
// 将value设置为ObjcAssociation的value。
value = entry.value();
// 将policy设置为ObjcAssociation的policy。
policy = entry.policy();
// 若是引用策略是Retain模式
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
// 调用value的retian方法,引用计数加一。
objc_retain(value);
}
}
}
//释放锁
}
// 若是值存在,而且引用策略是AutoRelease模式。
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
// 调用value的autorelease方法,将value设置为autorelease。
objc_autorelease(value);
}
// 返回取到的关联对象
return value;
}
复制代码
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
复制代码
在retian的时候也判断了是否为Tagged Pointer。
这里会调用一次obj的retian方法,引用计数会加一。
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
复制代码
与retian几乎相同,调用的是obj的autorelease方法。
void _object_remove_assocations(id object) {
// 声明一个vector容器,key类型是ObjcAssociation,value类型是ObjcAllocator<ObjcAssociation>,这是一个构造器结构体,在runtime时内部使用。
// vector在C++中是一个动态长度的顺序容器。
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
//建立一个AssociationsManager并加锁。
AssociationsManager manager;
// 获取AssociationsHashMap。
AssociationsHashMap &associations(manager.associations());
// 若是AssociationsHashMap中没有没有元素,则结束流程。
if (associations.size() == 0) return;
// 经过object构造disguised_object。
disguised_ptr_t disguised_object = DISGUISE(object);
// 在AssociationsHashMap中查找disguised_object对应的ObjectAssociationMap。
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是ObjectAssociationMap存在。
if (i != associations.end()) {
// 得到ObjectAssociationMap。
ObjectAssociationMap *refs = i->second;
// 循环迭代ObjectAssociationMap
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
//将全部的ObjectAssociation都存入vector中。
elements.push_back(j->second);
}
// 释放ObjectAssociationMap的内存空间。
delete refs;
// 在AssociationsHashMap中删除这个ObjectAssociationMap元素。
associations.erase(i);
}
}
// 循环释放vector中保存的每个ObjectAssociation
for_each(elements.begin(), elements.end(), ReleaseValue());
}
复制代码
//文件:runtime.h
/** * Destroys an instance of a class without freeing memory and removes any * associated references this instance might have had. * 摧毁一个实例对象,不会释放和移除任何此实例对象的关联对象。 * * @param obj The class instance to destroy. * * @return \e obj. Does nothing if \e obj is nil. * * @note CF and other clients do call this under GC. */
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj)
OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
/*********************************************************************** * object_dispose * fixme * Locking: none 文件:objc-runtime-new.m **********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
// ===============文件:objc-private.h ========================
inline void
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);
}
}
// ===============文件:NSObject.mm ========================
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
复制代码
从上面的代码能够看出,当NSObject执行dealloc的时候,并不会清理掉关联对象,因此关联对象是须要咱们手动维护的,用完及时清理,避免出现内存泄露。