【iOS】 分类 拓展 关联对象

【iOS】 分类 拓展 关联对象

文章目录

前言

之前讲过有关于类对象的内容,这里学习一下有关于类的分类拓展和关联对象的内容:

拓展

这里我们看一下下面这段代码转义程我们的cpp文件:

objc 复制代码
@interface CJLPerson ()
@property (nonatomic, copy) NSString* name;
- (void)saygogogogo;
@end

这里我们可以看到它被直接存储到了成员变量表中.

方法也是这样直接被添加到metholist

  • 类的扩展 在编译器 会作为类的一部分,和类一起编译进来
  • 类的扩展只是声明依赖于当前的主类,没有.m文件,可以理解为一个·h文件

分类

我们看一下分类的编译后的结构体

发现这个分类的结构体中有类指针,有实例方法表,类方法表,协议表,属性列表,但是没有类有的成员变量表:

这里就说明了

  • 我们不可以在类中定义成员变量.(因为没有成员变量表)

  • 可以声明一个属性,但是只会生成这些属性的getter和setter方法的声明,并不会自动实现这些方法。也就是说,如果你在分类中添加了一个属性,你还需要自己去实现这个属性的getter和setter方法。

  • 既然没有成员变量表,怎么实现我们的一个属性呢,通过关联对象来实现.

分类与拓展的区别

分类

  • 专门用来给类添加新的方法

  • 不能给类添加成员属性,添加了成员属性,也无法取到

  • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写setter、getter方法

  • 分类中用 @property 定义变量,只会生成变量的setter、getter方法的声明不能生成方法实现 和 带下划线的成员变量

拓展

  • 可以说成是特殊的分类 ,也可称作 匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法
  • 拓展只可以在本类中使用

关联对象

前面讲过可以通过runtime来给分类添加属性,现在我们就来了解一下有关于这里的关联对象的一个内容.

objc 复制代码
@interface CJLPerson (Test)
@property (nonatomic, copy) NSString* name;
@end


- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    return;
}

我们先讲一下有关于这个方法的几个参数objc_setAssociatedObject

  • 参数一:要关联的对象,即给谁添加关联属性
  • 参数二:标识符,方便下次查找
  • 参数三:value
  • 参数四:属性的策略,即nonatomic、atomic、assign等

下面这个图展示处理所有对象关联对象的一个属性类型

这里我们来详细了解一下有关于这部分的知识:

objc 复制代码
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);//接口隔离原则
}

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return; // 先处理值,如果为nil没有必要处理

    if (object->getIsa()->forbidsAssociatedObjects()) //检查对象所属的类是否禁止关联属性(例如 NSWindow 等系统类),若禁止则触发崩溃
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    DisguisedPtr<objc_object> disguised{(objc_object *)object};//将对象指针 object 转换为 DisguisedPtr,通过位操作隐藏指针值,避免内存分析工具直接暴露关联关系
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false; // 设置是否为第一次关联
    {
        AssociationsManager manager; //获取全局关联管理器,这个并不是单利,可以创建多次
        AssociationsHashMap &associations(manager.get()); //获得对应的一个hashmap,associations 是全局唯一的 AssociationsHashMap,存储所有对象的关联数据

        if (value) {  //处理键值对,更新值的大小,或者是插入一个新的值
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); //返回的结果是一个类对
            if (refs_result.second) {//如果是第一次关联
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;  //得到一个空的桶子,找到引用对象类型,即第一个元素的second值
            auto result = refs.try_emplace(key, std::move(association)); //查找当前的key是否有association关联对象
            if (!result.second) { //如果结果不存在
                association.swap(result.first->second);
            }
        } else { //当value为nil的时候,将key从hash表中移出
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        object->setHasAssociatedObjects(); //若为首次关联,调用 setHasAssociatedObjects() 设置对象的 has_assoc 标志位(在对象释放时触发关联对象的清理)

    // release the old value (outside of the lock).
    association.releaseHeldValue(); //根据策略释放旧值的引用计数(例如 OBJC_ASSOCIATION_RETAIN 会调用 release)
}

步骤

  • 先处理值,如果为nil没有必要处理.检查对象所属的类是否禁止关联属性(例如 NSWindow 等系统类),若禁止则触发崩溃
  • 获取全局关联管理器,获得对应的一个hashmap,associations 是全局唯一的 AssociationsHashMap,存储所有对象的关联数据
  • 处理键值对,如果插入和更新各自处理,如果值为nil就直接移出hash表的数据
  • 若为首次关联,调用 setHasAssociatedObjects() 设置对象的 has_assoc 标志位(在对象释放时触发关联对象的清理)
  • 根据策略释放旧值的引用计数
哈希表(AssociationsHashMap)

从上面的流程我们可以看出关联对象的一个内存管理全部归我们的一个AssociationsHashMap管理,而不是由当前这个类来管理,也不是它的分类管理

我从上面的流程可以看出他的核心内容是一个AssociationHashMap我们现在主要了解一个这个hashMap的一个结构:

objc 复制代码
class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
};

这里我们可以看到,我们的AssociationHashMap是从这个由static修饰的静态全局变量中取出来的

objc 复制代码
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); }
    };

从上面的结构体可以看出AssociationsHashMap内部维护了一个 ObjectAssociationMap哈希表:

这里的ObjectAssociationMap内部中关联了ObjcAssociationkey的一个关系

objc 复制代码
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); }
    };

这时候笔者给出一张别人的思维导图来说明这几个表的一个关系:

接下来笔者主要讲一下这里几个map的一个联系与不同:

  • 可以有无限多个Manger,但是我们的AssociationsHashMap只有一个,都是通过manger来获取的

  • 第一个map对应的是每一个类都作为一个key拥有这不同的一个ObjectAssiciationMap,每个类维护属于自己的那一个关联对象,就好比person类维护person类的,teacher维护teacher类的,.

  • 第二个ObjectAssiciationMap的key的类型为const void 再加上我们对于objc_setAssociatedObject(<id _Nonnull object>, <const void * _Nonnull key>, <id _Nullable value>, <objc_AssociationPolicy policy>)这个就相当于我们前面设置的那个字符串.value的类型是ObjcAssociation

  • 最后的value由两个成员变量,一个是内存管理的一个策略,一个是一个value.

这里就是这里我们发现,我们的key是很重要的,我们不可以把多个value关联到同一个key中,所以我们对于key有以下几个用法:

  • 采用getter方法关联:
    • objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  • 采用属性名
    • objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
  • 使用指针名
    • static void *MyKey = &MyKey; objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    • static void *MyKey = &MyKey; 创建一个静态的 void* 指针变量 MyKey,并将其地址赋给自身。这种写法通过静态变量的内存地址唯一性保证键值的全局唯一性
  • 使用static字符作为key
    • static char MyKey; objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)'

大致工作流程

set
  • 调用 objc_setAssociatedObject。
  • AssociationsManager 查找或创建与目标对象相关的 ObjectAssociationMap。
  • 在 ObjectAssociationMap 中查找或创建对应的 ObjcAssociation。
  • 将关联值和存储策略设置到 ObjcAssociation 中
get
  • 调用 objc_getAssociatedObject。
  • AssociationsManager 查找或创建与目标对象相关的 ObjectAssociationMap。
  • 在 ObjectAssociationMap 中查找或创建对应的 ObjcAssociation。
  • 返回从ObjcAssociation返回的value
remove
  • 调用 objc_removeAssociatedObjects 或 objc_setAssociatedObject 设置为 nil。
  • AssociationsManager 查找与目标对象相关的 ObjectAssociationMap。
  • 从 ObjectAssociationMap 中移除对应的 ObjcAssociation。
  • 如果 ObjectAssociationMap 为空,可能会移除整个映射以释放资源。

关联对象的释放时机

对象销毁的时候会调用一个dealloc函数,这个函数会执行下面几个方法:

  • 1、C++函数释放 :objc_cxxDestruct
  • 2、移除关联属性:_object_remove_assocations
  • 3、将弱引用自动设置nil:weak_clear_no_lock(&table.weak_table, (id)this);
  • 4、引用计数处理:table.refcnts.erase(this)
  • 5、销毁对象:free(obj)

所以这里会自动移除这里的关联对象的属性.

所以我们不需要手动释放这里的关联对象.

总结

这里我们就大致明白了我们关联对象的一个set过程,get过程其实也是类似在这个二层hash表中进行一个检索.这里笔者就不多讲述细节了.直接用一张图来总结一下:

相关推荐
HeShen.11 分钟前
机器学习Python实战-第三章-分类问题-4.支持向量机算法
python·机器学习·支持向量机·分类·sklearn
云天徽上23 分钟前
【机器学习案列-22】基于线性回归(LR)的手机发布价格预测
人工智能·算法·机器学习·智能手机·数据挖掘·数据分析·线性回归
不吃香菜?4 小时前
SVM实战:从理论到鸢尾花数据集的分类可视化
算法·支持向量机·分类
安和昂6 小时前
【iOS】消息流程探索
ios
数据最前线14 小时前
Doris索引机制全解析,如何用高效索引加速数据分析
数据挖掘·数据分析
StreetWolf14 小时前
数据处理1
数据挖掘·数据分析
DeyouKong18 小时前
Go反射-通过反射调用结构体的方法(带入参)
开发语言·ios·golang
黄昏ivi18 小时前
优化问题中变量分类与作用分析
人工智能·分类·数据挖掘
缘友一世21 小时前
逻辑回归的多分类实战:以鸢尾花数据集为例
算法·分类·逻辑回归