IT评测·应用市场-qidao123.com技术社区

标题: 【iOS】——分类拓展关联对象 [打印本页]

作者: 莱莱    时间: 2025-1-10 07:40
标题: 【iOS】——分类拓展关联对象
分类

OC的动态特性答应使用种别为现有的类添加新方法而且不必要创建子类,不必要访问原有类的源代码。通过使用种别即可动态为现有的类添加新方法,而且可以将类界说模块化分布到多个相干文件。

示例代码如下:
  1. @interface NSString (MyCategory)
  2. - (NSString *)reverseString;
  3. @end
  4. @implementation NSString (MyCategory)
  5. - (NSString *)reverseString {
  6.     NSMutableString *reversedString = [[NSMutableString alloc] init];
  7.     for (NSInteger i = self.length - 1; i >= 0; i--) {
  8.         [reversedString appendString:[self substringWithRange:NSMakeRange(i, 1)]];
  9.     }
  10.     return reversedString;
  11. }
  12. @end
复制代码
分类应用场景:

分类的界说如下:
  1. struct category_t {
  2.     // 分类名称
  3.     const char *name;
  4.     // 分类所属的类
  5.     classref_t cls;
  6.     // 实例方法列表
  7.     struct method_list_t *instanceMethods;
  8.     // 类方法列表
  9.     struct method_list_t *classMethods;
  10.     // 协议列表
  11.     struct protocol_list_t *protocols;
  12.     // 实例属性列表
  13.     struct property_list_t *instanceProperties;
  14.     // 类属性列表(可能不存在)
  15.     struct property_list_t *_classProperties;
  16.     // 获取实例方法或类方法列表
  17.     method_list_t *methodsForMeta(bool isMeta) {
  18.         if (isMeta) return classMethods;
  19.         else return instanceMethods;
  20.     }
  21.     // 获取实例属性或类属性列表
  22.     property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
  23. };
复制代码
从布局体可以看出,分类能

但是不能添加实例变量,即无法主动生成实例变量的setter和getter方法
扩展

扩展与种别相似,偶然候被称为匿名分类。但是两者实质上不是一个内容。
扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对体系的类使用扩展。
同时与分类不同,扩展不但可以声明方法,还可以声明成员变量,这是分类所做不到的。
示例代码如下:
  1. #import "Car1.h"
  2. @interface Car1 ()
  3. @property (nonatomic, copy) NSString *color;
  4. - (void)drive:(NSString *)owner;
  5. @end
  6. #import "Car1.h"
  7. #import "Car1+drive.h"
  8. @implementation Car1
  9. - (void)drive {
  10.     NSLog (@"%@汽车在路上跑", self);
  11. }
  12. - (void)drive:(NSString*)owner {
  13.     NSLog(@"%@正在驾驶%@汽车在路上跑", owner, self);
  14. }
  15. - (NSString*)description {
  16.     return [NSString stringWithFormat:@"<Car[_brand = %@, _model = %@, _color = %@]>",self.brand, self.model, self.color];
  17. }
  18. @end
复制代码
两者区别


关联对象

关联对象答应你为一个对象添加额外的属性,即使这个对象自己没有界说这些属性。
因此,可以使用关联对象给分类添加属性
下面是关联对象的API
  1. //添加关联对象
  2. void objc_setAssociatedObject(id object, const void * key, id value, objc AssociationPolicy policy)
  3. //获得关联对象
  4. id objc_getAssociatedObject(id object, const void * key)
  5. //移除所有的关联对象
  6. void objic_removeAssociatedObjects(id object)
复制代码

关联战略如下:
  1. typedef OBJC ENUM(uintptr_t, objc_AssociationPolicy) {
  2.         OBJC_ ASSOCIATION_ASSIGN = 0, //指定个弱引用相关联的对象
  3.         OBJC_ ASSOCIATION_RETAIN_NONATOMIC = 1; //指定相关对象的强引用, 非原子性
  4.         OBJC_ ASSOCIATION_COPY_NONATOMIC = 3; //指定相关的对象被复制, 非原子性
  5.         OBJC_ ASSOCIATION_RETAIN = 01401; //指定相关对象的强引用,原子性
  6.         OBJC_ ASSOCIATION_COPY = 01403; //指定相关的对象被复制, 原子性
  7. };
复制代码
给key设置值一般来说有三种方法
  1. static const void *NameKey = &NameKey;
  2. static const void *WeightKey = &WeightKey;
复制代码
  1. #define NameKey = @"name";
  2. #define WeightKey = @"weight";
复制代码
  1. @selector(name)//直接用属性名对应的get方法的selector,有提示不容易写错。并且get方法隐藏参数cmd 可以直接用,看上去就会更加简洁
复制代码
下面是示例代码:
  1. #import <UIKit/UIKit.h>
  2. #import "objc/runtime.h"
  3. NS_ASSUME_NONNULL_BEGIN
  4. @interface UIView (defaultColor)
  5. @property (nonatomic, strong)UIColor* defaultColor;
  6. @end
  7. NS_ASSUME_NONNULL_END
  8. #import "UIView+defaultColor.h"
  9. @implementation UIView (defaultColor)
  10. @dynamic defaultColor;
  11. static char kDefaultColorKey;
  12. - (void)setDefaultColor:(UIColor *)defaultColor {
  13.     //objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  14.     objc_setAssociatedObject(self, @selector(defaultColor), defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  15. }
  16. - (id)defaultColor {
  17.     //return objc_getAssociatedObject(self, &kDefaultColorKey);
  18.     return objc_getAssociatedObject(self, _cmd);
  19. }
  20. @end
复制代码
通过上面的关联对象我们就给体系提供的UIView设置了默认颜色的属性

   _cmd 是一个特别的参数,它代表当前正在执行的方法的选择器(selector)。选择器是一个字符串,它标识了方法的名称。
  _cmd 可以让方法在执行过程中获取自身的信息,例如方法名称
  关联对象底层探索

实现关联对象技术的核心对象有
objc_setAssociatedObject

起首来看创建关联对象的函数,源码如下:
  1. void
  2. _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
  3. {
  4.     //isa有一位信息为禁止关联对象,如果设置了,直接报错
  5.     if (!object && !value) return;
  6.     // 判断runtime版本是否支持关联对象
  7.     if (object->getIsa()->forbidsAssociatedObjects())
  8.         _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
  9.     // 将 object 封装成 DisguisedPtr 目的是方便底层统一处理
  10.     DisguisedPtr<objc_object> disguised{(objc_object *)object};
  11.     // 将 policy和value 封装成ObjcAssociation,目的是方便底层统一处理
  12.     ObjcAssociation association{policy, value};
  13.     // (如果有新值)保留锁外的新值。
  14.     // retain the new value (if any) outside the lock.
  15.     // 根据传入的缓存策略,创建一个新的value对象
  16.     association.acquireValue();
  17.     bool isFirstAssociation = false;
  18.     {
  19.     //调用构造函数,构造函数内加锁操作
  20.         AssociationsManager manager;
  21.         // 创建一个管理对象管理单例,类AssociationsManager管理一个锁/哈希表单例对。分配一个实例将获得锁
  22.         // 并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
  23.         
  24.         //获取全局的HasMap
  25.         // 全场唯一
  26.         AssociationsHashMap &associations(manager.get());
  27.         
  28.         if (value) {
  29.             //去关联表中找对象对应的关联对象表,如果没有内部会重新生成一个
  30.             
  31.             auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
  32.             //如果没有找到
  33.             if (refs_result.second) {
  34.                 /* it's the first association we make */
  35.                 // 这是我们建立的第一个关联
  36.                 //说明是第一次设置关联对象,把是否关联对象设置为YES
  37.                 isFirstAssociation = true;
  38.             }
  39.             // 建立或替换关联
  40.             /* establish or replace the association */
  41.             // 获取ObjectAssociationMap中存储值的地址
  42.             auto &refs = refs_result.first->second;
  43.             // 移除之前的关联,根据key
  44.             // 将需要存储的值存放在关联表中存储值的地址中
  45.             // 同时会根据key去查找,如果查找到`result.second` = false ,如果找不到就创建`result.second` = true
  46.             // 创建association时,当(association的个数+1)超过3/4,就会进行两倍扩容
  47.             auto result = refs.try_emplace(key, std::move(association));
  48.             if (!result.second) {
  49.                 // 交换association和查询到的`association`
  50.                 // 其实可以理解为更新查询到的`association`数据,新值替换旧值
  51.                 association.swap(result.first->second);
  52.             }
  53.         } else {
  54.             // 这里相当于传入的nil,移除之前的关联
  55.             // 到AssociationsHashMap找到ObjectAssociationMap,将传入key对应的值变为空。
  56.             // 查找disguised 对应的ObjectAssociationMap
  57.             auto refs_it = associations.find(disguised);
  58.             // 如果找到对应的 ObjectAssociationMap 对象关联表
  59.             if (refs_it != associations.end()) {
  60.                 // 获取 refs_it->second 里面存放了association类型数据
  61.                 auto &refs = refs_it->second;
  62.                 // 根据key查询对应的association
  63.                 auto it = refs.find(key);
  64.                 if (it != refs.end()) {
  65.                     // 如果找到,更新旧的association里面的值
  66.                     association.swap(it->second);
  67.                     refs.erase(it);
  68.                     if (refs.size() == 0) {
  69.                         // 如果该对象关联表中所有的关联属性数据被清空,那么该对象关联表会被释放
  70.                         associations.erase(refs_it);
  71.                     }
  72.                 }
  73.             }
  74.         }
  75.     }
  76.     // 在锁外面调用setHasAssociatedObjects,因为如果对象有一个,这个//将调用对象的noteAssociatedObjects方法,这可能会触发initialize,这可能会做任意的事情,包括设置更多的关联对象。
  77.     if (isFirstAssociation)
  78.         object->setHasAssociatedObjects();
  79.     // release the old value (outside of the lock).
  80.     // 释放旧的值(在锁外部)
  81.     association.releaseHeldValue();
  82. }
复制代码

objc_getAssociatedObject

获取关联对象的源码如下:
  1. id
  2. _object_get_associative_reference(id object, const void *key)
  3. {
  4.     ObjcAssociation association{};//创建空的关联对象
  5.     {
  6.         AssociationsManager manager;//创建一个AssociationsManager管理类
  7.         AssociationsHashMap &associations(manager.get());//获取全局唯一的静态哈希map
  8.         AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到迭代器,即获取buckets
  9.         if (i != associations.end()) {//如果这个迭代查询器不是最后一个 获取
  10.             ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  11.             ObjectAssociationMap::iterator j = refs.find(key);//根据key查找ObjectAssociationMap,即获取bucket
  12.             if (j != refs.end()) {
  13.                 association = j->second;//获取ObjcAssociation
  14.                 association.retainReturnedValue();
  15.             }
  16.         }
  17.     }
  18.     return association.autoreleaseReturnedValue();//返回value
  19. }
复制代码

objc_removeAssociatedObjects

移除关联对象的源码如下:
  1. // 与设置/获取关联引用不同,此函数对性能敏感,因为原始isa对象(如OS对象)不能跟踪它们是否有关联对象。
  2. void
  3. _object_remove_assocations(id object, bool deallocating)
  4. {
  5.     ObjectAssociationMap refs{};
  6.     {
  7.         AssociationsManager manager;
  8.         AssociationsHashMap &associations(manager.get());
  9.         AssociationsHashMap::iterator i = associations.find((objc_object *)object);
  10.         if (i != associations.end()) {
  11.             refs.swap(i->second);
  12.             // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
  13.             //如果我们没有回收,那么SYSTEM_OBJECT关联会被保留。
  14.             bool didReInsert = false;
  15.             if (!deallocating) {
  16.                 for (auto &ref: refs) {
  17.                     if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
  18.                         i->second.insert(ref);
  19.                         didReInsert = true;
  20.                     }
  21.                 }
  22.             }
  23.             if (!didReInsert)
  24.                 associations.erase(i);
  25.         }
  26.     }
  27.     // Associations to be released after the normal ones.
  28.     // 在正常关联之后释放关联。
  29.     SmallVector<ObjcAssociation *, 4> laterRefs;
  30.     // release everything (outside of the lock).
  31.     // 释放锁外的所有内容。
  32.     for (auto &i: refs) {
  33.         if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
  34.             // If we are not deallocating, then RELEASE_LATER associations don't get released.
  35.             //如果我们不是在释放,那么RELEASE_LATER关联不会被释放
  36.             if (deallocating)
  37.                 laterRefs.append(&i.second);
  38.         } else {
  39.             i.second.releaseHeldValue();
  40.         }
  41.     }
  42.     for (auto *later: laterRefs) {
  43.         later->releaseHeldValue();
  44.     }
  45. }
复制代码

总结


Category的方法会“覆盖”掉原来类的同名方法?


关联对象被存储在什么地方,是不是存放在被关联对象自己的内存中?

关联对象存放在名为ObjectAssociationMap的哈希表中,存放关联对象的哈希表又被存放在名为AssociationsHashMap的哈希表中,通过AssociationsManager来管理。也就是说全部对象的关联对象都存在一个全局map内里。而map的的key是这个对象的指针地点,而这个map的value又是别的一个全局map,内里保存了关联对象的key。
关联对象的生命周期是怎样的,什么时候被开释,什么时候被移除?

关联对象的开释时机与移除时机并不总是同等。关联对象的生命周期取决于关联战略和目的对象的生存期。弱引用战略的关联对象会随着目的对象的开释而被开释,而强引用战略和复制引用战略的关联对象会继续存在,直到它们的引用计数降为 0。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4