iOS 通过 runtime 的 API 可以给分类添加属性, 关联属性总共有下边 3 个 API
- /// 获取某个对象的关联属性
- idobjc_getAssociatedObject(idobject,constvoid*key){
- return_object_get_associative_reference(object, (void*)key);
- }
- /// 给某个对象添加关联属性
- voidobjc_setAssociatedObject(idobject,constvoid*key, idvalue, objc_AssociationPolicy policy){
- _object_set_associative_reference(object, (void*)key,value, policy);
- }
- /// 移除对象所有的关联属性
- voidobjc_removeAssociatedObjects(idobject)
通过 runtime 的源码可以看出关联属性并没有添加到 category_t(分类)里边, 运行时也不会合并到元类对象里边, 而是存储在一个全局的 AssociationsManager 里边, 下边是这个 AssociationsManager 包含的层级关系.
所有的关联属性 和 获取关联属性 移除关联属性都是通过一个 AssociationsManager 来操作, 类似于 OC 中 NSFileManager 的角色, 通过传递进来的对象作为地址 取出这个对象所对应的关联列表, 然后通过 key 取出这个关联列表的关联属性 ObjcAssociation, ObjcAssociation 包含了关联策略 和 关联值.
下边我会通过解读源码 来分析 AssociationsManager 是如何要关联的值和对象建立联系的.
AssociationsManager 是一个 C++ 的类 用来进行对关联对象的属性添加 和 查找 移除等操作
- classAssociationsManager{
- staticspinlock_t_lock;
- staticAssociationsHashMap *_map;// associative references: object pointer -> PtrPtrHashMap. 这个_ map 里边存储的有关联列表
- public:
- AssociationsManager() { _lock.lock(); }
- ~AssociationsManager() { _lock.unlock(); }
- AssociationsHashMap &associations(){// 可以看成是只初始化一次 类似与单例
- if(_map ==NULL)
- _map =newAssociationsHashMap();
- return*_map;
- }
- };
关联列表是一个 hashMap 类似于 OC 的 NSDictionary , 其中用 disguised_ptr_t 作为 key , ObjectAssociationMap * 作为一个 value
disguised_ptr_t 是 uintptr_t 的类型
intptr_t 和 uintptr_t 类型用来存放指针地址. 它们提供了一种可移植且安全的方法声明指针, 而且和系统中使用的指针长度相同, 对于把指针转化成整数形式来说很有用.
可以把 disguised_ptr_t 理解为一个指针类型的变量
- classAssociationsHashMap:publicunordered_map {
- public:
- void*operatornew(size_tn){return::malloc(n); }
- voidoperatordelete(void*ptr){ ::free(ptr); }
- };
ObjectAssociationMap 也是一个 HashMap 存放的是 一个 void * key 就是关联属性时传进来的 key , ObjcAssociation 存放的关联属性策略和值的信息
- classObjectAssociationMap:publicstd::map {
- public:
- void*operatornew(size_tn){return::malloc(n); }
- voidoperatordelete(void*ptr){ ::free(ptr); }
- };
ObjcAssociation 关联属性信息类 存放了关联策略 和 传递进来关联的值 id 类型
- classObjcAssociation{
- uintptr_t_policy;
- id _value;
- public:
- ObjcAssociation(uintptr_tpolicy, id value) : _policy(policy), _value(value) {}
- ObjcAssociation() : _policy(0), _value(nil) {}
- uintptr_tpolicy()const{return_policy; }
- idvalue()const{return_value; }
- boolhasValue(){return_value != nil; }
- };
下边是对 objc_getAssociatedObject , objc_setAssociatedObject , objc_removeAssociatedObjects 具体实现的分析
objc_setAssociatedObject 添加关联属性的 API
- void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
- // retain the new value (if any) outside the lock.
- /// 旧的关联对象 因为关联属性时如果传 nil 可能会替换旧的关联属性 , 这就是移除某个关联属性时传 nil 的原因
- ObjcAssociation old_association(0, nil);
- id new_value = value ? acquireValue(value, policy) : nil;
- {
- AssociationsManager manager;
- /// 获取关联属性列表 , 取出来的列表是以对象为单位的 , 即某个对象的关联列表 , 这样就可以单独的关联某个对象的关联属性 而不与其他对象隔离开
- AssociationsHashMap &associations(manager.associations());
- /// 将要添加关联属性的对象产生一个内存地址 做 key 存储 它的关联属性
- disguised_ptr_t disguised_object = DISGUISE(object);
- /// 如果要关联的值不为空 , 不为空时 就需要判断这个属性和 key 是不是第一天添加 , 即 void *key, id value 都是第一次传递进来
- if(new_value) {
- 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;// 取出旧值 后边对这个旧值进行 release 操作
- /// 将新值存放到 key 对应的字典中去
- j->second = ObjcAssociation(policy, new_value);
- }else{/// 没有旧值直接将新值添加到字典里
- (*refs)[key] = ObjcAssociation(policy, new_value);
- }
- }else{
如果 key 对象的字典不存在 就创建一个字典 (hashMap 类似于字典的功能, 本文为了方便理解将它称为字典)
- ObjectAssociationMap *refs =newObjectAssociationMap;
- associations[disguised_object] = refs;
- /// 将要关联属性和策略封装到一个 ObjcAssociation 类里边 并根据 key 添加到这个字典里
- (*refs)[key] = ObjcAssociation(policy, new_value);
- object->setHasAssociatedObjects();
- }
- }else{
- /// 如果添加关联的属性为空时 就需要取出之前关联的值 并把它擦除掉 相当于 removeObjectForKey
- /// 还是根据对象内存地址找到它的关联属性列表 , 然后通过 key 找到它关联属性的实体(ObjcAssociation 这个类) 最后擦除掉 相当于 free 从内存中移除
- 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);
- }
objc_getAssociatedObject 关联对象取值的操作
- id_object_get_associative_reference(idobject,void*key) {
- idvalue =nil;
- uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
- {
- /// 还是通过 AssociationsManager 找到所有关联对象类别 , 然后通过传入 object 找到某个对象的关联列表 , 然后通过 key 找到这个对象关联属性列表的某个实体(ObjcAssociation) 最后根据关联策略返回这个属性的值
- 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();
- /// 取出关联值和策略 发送消息 类似与 [obj retain]
- if(policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
- }
- }
- }
- /// 如果这个对象是延时释放的类型 类似与 OC Array String 这些不是 alloc 来的对象 都要执行 [obj autorelease]来释放
- if(value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
- ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
- }
- returnvalue;
- }
objc_removeAssociatedObjects 移除该对象所有的关联属性列表
- 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);
- /// 如果这个对象有关联的属性列表 那么久便利它关联的属性列表 然后通过便利将这些关联内容 一个个从字典里边擦除 先擦除对象列表关联的属性列表 然后将这个对象关联属性的 hashMap 擦除掉 相当于 [dict removeAllObjects] 然后再从全局 AssociationsManager 移除 这个对象关联的字典 , 又相当于 从一个全局大字典里 把 dict 这个对象的小字典 给移除了
- 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());
- }
以上代码看起来并不难 除了一些 C++ 语法难以理解外 也并不需要完全知道每行代码怎么实现 , 大概思路就是 通过全局大字典 , 找到某个对象相关的小字典 , 然后这个小字典里存放了 一个 key 对应一个属性值, 最后取出这个管理属性的策略和值
写到最后 AssociationsManager 这个类不是一个单例类
- class AssociationsManager {
- static spinlock_t _lock;
- static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
- public:
- AssociationsManager() { _lock.lock(); } // 默认无参构造函数 对象创建时自动调用 执行加锁 这样多个线程访问 _map 时不会出现问题
- ~AssociationsManager() { _lock.unlock(); } // 析构函数 对象是否时候进行解锁的操作
- /// 可以看做单例对象 在 AssociationsManager 创建时候 加锁 当 AssociationsManager 释放时候 解锁 , 防止多线程访问时候 对同一个 _map 多次创建 是一种懒汉模式单例
- AssociationsHashMap &associations() {
- if (_map == NULL)
- _map = new AssociationsHashMap();
- return *_map;
- }
- };
它里边有个 spinlock_t 锁 对 _map 这个全局唯一的实例 进行加锁和解锁 , 由于懒汉模式的单例 需要在多个线程访问 _map 时候进行加锁保护
其实做为一个开发者, 有一个学习的氛围跟一个交流圈子特别重要, 这是一个我的 iOS 学习交流群 651612063, 不管你是小白还是大牛欢迎入驻, 大家一起交流学习, 加群私聊 (小狄) 就可以领取 2018 最全梳理的面试宝典和资料). 同时想要找工作的也可以私聊小编.
来源: http://www.jianshu.com/p/5269b4852625