前言
AssociationedObject多用于在Category中为特定类扩展成员变量,也有用于在运行时为某些对象动态创建成员变量。AssociationedObject可以说是一种特殊的成员变量。
这篇文章是来详细解释AssociationedObject的实现原理,篇幅较长。
相关方法
objc_AssociationPolicy
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_ASSIGN
将关联引用描述为弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC
将关联引用描述为强引用,并且非原子性。
OBJC_ASSOCIATION_COPY_NONATOMIC
将关联引用描述为拷贝引用,并且非原子性。
OBJC_ASSOCIATION_RETAIN
将关联引用描述为强引用,并且为原子性。
OBJC_ASSOCIATION_COPY
将关联引用表述为拷贝引用,并且为原子性。
这一段比较好理解,与Property一样,关联引用也可以设置引用描述。
objc_setAssociatedObject
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_getAssociatedObject
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
关联对象值的获取方法。
通过关联Key,在目标对象(object)中,获取到关联对象的值(返回值)。
objc_removeAssociatedObjects
/**
* 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版的代码。
objc_setAssociatedObject
在这个方法中有很多的数据结构,首先介绍下数据结构,帮助理解,也可以先往下翻看主要流程,有看不明白的再回来查找。
AssociationsManager
// 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的全局变量引用。
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>
解释下这个泛型的意思
- _Key(key_type)对应disguised_ptr_t,disguised_ptr_t实际上是unsigned long类型,这是用来存放指针的。
- _Tp(mapped_type)对应ObjectAssociationMap *,这个表明了Map中存储的值类型,后面会介绍ObjectAssociationMap。
- _Hash(hasher)对应DisguisedPointerHash,提供hash算法。
- _Pred(key_equal)对应的是DisguisedPointerEqual,提供equal算法(两个指针相同则相等)。
- _Alloc(allocator_type)对应的是构造函数的类型,这里构造函数类型是ObjcAllocator >,也是一个泛型,用std::pair实现Key是disguised_ptr_t,Value是ObjectAssociationMap *的键值对。
它实现了创建和删除的功能。
disguised_ptr_t
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)指针。
ObjectAssociationMap
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,这个上面提到过,用来存放关联对象的值与关联策略。
_Compare(key_compare)对应的是ObjectPointerLess,提供比较算法(比对指针地址)。
- _Allocator(allocator_type)对应的是ObjcAllocator >,用std::pair实现Key是void * const(内存地址),Value是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; }
};
这是关联对象的结构,存储着关联策略_policy
和关联对象的值_value
。
acquireValue
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方法生成新的拷贝,其他的策略不作处理。
setHasAssociatedObjects
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的概念,Tagged Pointer是一种为了节约内存占用的策略,它的原理是将指针对象的数据直接存于指针中,在64位系统中被引入。
比如,在64位系统中,一个指针为8字节,当指针关联的值小于8位的时候,系统会将指针转化成Tagged Pointer,并在最后一个bit位加入TaggedPoint标识,这个指针的结构就变成了“0x存储数据+TaggedPoint标识”的结构。
0xb000000000000032
|---------------|-|
0x|----存储数据----|标识|
这么做的好处是,减少了内存的占用,又因为数据不需要放入堆中,所以不需要malloc和free,所以读取和运行速度都得到了提升。
这个时候指针已经变成了一个值,而不再是一个地址,所以它也没有isa指针,所以要先做判断。
ReleaseValue 和 releaseValue
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(关联对象)的关系。
objc_getAssociatedObject
下面分析的是关于关联对象的取值逻辑,大部分结构体部分在设置值部分都说了,又遇到不理解的回头去看,这里直接将主要实现了。
主要实现
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;
}
objc_retain
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
在retian的时候也判断了是否为Tagged Pointer。
这里会调用一次obj的retian方法,引用计数会加一。
objec_autorelease
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
与retian几乎相同,调用的是obj的autorelease方法。
objc_removeAssociatedObjects
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的时候,会清理掉关联对象,并不需要手动维护关联对象。
总结
- 可以看出Runtime对于关联对象的管理都是线程安全的,增删查改都是加锁的。
- 在App运行期间,由AssociationsHashMap来管理所有被添加到对象中的关联对象。
- 当NSObject dealloc时,会释放持有的关联对象。