【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表中进行一个检索.这里笔者就不多讲述细节了.直接用一张图来总结一下:

相关推荐
wjm04100610 小时前
ios内存管理
ios·objective-c·swift·客户端开发
黑科技iOS上架11 小时前
ios应用被封号后再次上架很难么?
经验分享·ios
高洁0115 小时前
医疗行业的数字孪生革命
python·机器学习·数据挖掘·transformer·知识图谱
柚鸥ASO优化16 小时前
一篇讲透安卓ASO!开发者千万别只盯着iOS了
android·ios·aso优化
黑科技iOS上架16 小时前
Swift Package Manager包管理工具的优缺点
经验分享·ios
dog25017 小时前
信号权重和流分类的对数规律
人工智能·分类·数据挖掘
现代野蛮人18 小时前
【深度学习】 —— 几种优化器对比实验
人工智能·深度学习·分类·tensorflow
林间码客19 小时前
03(扩展)回归决策树(Regression Decision Tree)
决策树·数据挖掘·回归
大熊猫侯佩20 小时前
Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”
ios·swift·编程语言
牛油果子哥q20 小时前
二叉树(Binary Tree)零基础精讲,树基础概念、树形分类、核心性质、递归/层序遍历、完整代码与面试考点全解
c++·面试·数据挖掘