Category使用关联对象生成属性的原理

Category关联对象: main.m文件:

#import #import "NSString+Category.h"int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); NSString *string = [NSString new]; string.newString = @"newString"; NSLog(@"%@", string.newString); } return 0; }

NSString+Category.h文件:
#import NS_ASSUME_NONNULL_BEGIN@interface NSString (Category) @property (nonatomic, copy) NSString *newString; @endNS_ASSUME_NONNULL_END

NSString+Category.m文件:
#import "NSString+Category.h" #import @implementation NSString (Category) - (NSString *)newString { return objc_getAssociatedObject(self, @"newString"); } - (void)setNewString:(NSString *)newString { objc_setAssociatedObject(self, @"newString", newString, OBJC_ASSOCIATION_COPY); } @end

通过关联对象的方式在运行添加处为类别来添加实例对象、getter和setter方法。

接下来从底层原理看一下,它是如何做到这一点的。

在runtime.h文件中第1658行
/** * 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);

从这里能看到函数的定义和函数注释,接下来我们看看函数的实现:
id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, (void *)key); }void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); }void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object); } }

我们先来看一下set方法,objc_setAssociatedObject这个方法调用的是_object_set_associative_reference,以下是_object_set_associative_reference的源代码实现:
_object_set_associative_reference
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 = https://www.it610.com/article/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); }

【Category使用关联对象生成属性的原理】我们来看个关键的地方:
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); } } }

从源代码中看,有个地方不太好理解:
AssociationsManager manager; AssociationsHashMap &associations(manager.associations());

每次函数判断完是否有新值之后,都会实例化一个manager,那么是怎么管理设置进入的属性呢,因为AssociationsHashMap的获取是一个单例对象,这样就可以管理以往加入的属性。
接下来看一下源码中,关联关系的几个类:
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; } };

在associations函数中可以看到那个单例HashMap, 还有AssociationsManagerLock这个自旋锁,用来保证线程安全。
AssociationsHashMap
class AssociationsHashMap : public unordered_map { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };

用来存储disguised_ptr_t和ObjectAssociationMap之间的映射。
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
_policy:用来存储关联的策略。例如OBJC_ASSOCIATION_COPY
_value:用来存储关联的对象。
根据以上几个类,我们就可以大概分析出_object_set_associative_reference的源代码流程,下图借鉴与此:http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/

Category使用关联对象生成属性的原理
文章图片
image.png
接下来,继续看_object_set_associative_reference源代码:
_object_get_associative_reference
id _object_get_associative_reference(id object, void *key) { id value = https://www.it610.com/article/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 = https://www.it610.com/article/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; }

通过分析过set函数之后,get函数逻辑就很简单了,首先实例一个AssociationsManager对象,然后去获取AssociationsHashMap对象,在通过AssociationsHashMap找到对应的ObjecAssociation,最后再从ObjcAssociation中取出对应的值,如果没有对应的关联的对象,那么返回nil。
还有一个函数,但是这个函数一般情况下,我们用不到,即使用不到,理解其源码,也能帮我们更好的理解其他相关联的源代码。
objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object); } }

首先判断对象是否存在并且判断对象是否有没有associatedObject,具体hasAssociatedObjects实现原理,可以查看这个文章:https://juejin.im/post/5cf4875651882544171c60d9
如果通过if判断之后,就进入了_object_remove_assocations函数:
void _object_remove_assocations(id object) { vector< ObjcAssociation,ObjcAllocator > 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()); }

跟以上两函数一样,但是一开始要创建一个vector,这个在后面游泳,接下来,实例一个AssociationsManager对象,通过AssociationsManager对象获取到AssociationsHashMap单例对象,通过对象获取disguised_ptr_t的值,然后在AssociationsHashMap中通过disguised_ptr_t对象找到ObjectAssociationMap,然后遍历ObjectAssociationMap,通过每个元素,找到最终的对象值。然后放到刚开始创建的vector里面,然后释放ObjectAssociationMap对象。释放完ObjectAssociationMap对象之后,将关联关系从associations中解除。最后把vector里面的对象都释放掉。
到此为止,Category关联对象生成属性的原理就基本分析完了,下面提供一个关联策略表格:
关联策略 等价属性 说明
OBJC_ASSOCIATION_ASSIGN @property(assign) 弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(strong, nonatomic) 非原子操作的强引用关联对象
OBJC_ASSOCIATION_RETAIN @property(strong, atomic) 原子操作的强引用关联对象
OBJC_ASSOCIATION_COPY @property(copy, atomic) 原子操作的复制关联对象
OBJC_ASSOCIATION_COPY_NONATOMIC @property(copy, nonatomic) 非原子操作的复制关联对象

    推荐阅读