【iOS】分类、扩展、关联对象

分类、扩展、关联对象

前言

最近的学习中笔者发现自己对于分类、扩展相关知识并不是很熟悉,刚好看源码类的加载过程中发现有类扩展与关联对象详解。本篇我们来探索一下这部分相关知识,首先我们要记住扩展是编译时就被添加在类中,而分类是在运行时才被整合到类信息中来的

分类

这里我们先来看看使用Clang编译之后,分类的底层结构struct category_t

这里我们来看看其中的内容,根据名称我们可以发现其中存储了++类指针、实例方法表、类方法表、协议表、属性列表++ ,但是并没有类中有的成员变量表 。其实看到这里我们就可以明白,我们不可以在分类中定义成员变量,原因很简单,这里面都没有成员变量表

这里还有一个结论:分类可以声明属性,并可以生成对应的set、get方法,但没有去实现该方法

分类加载流程:

  • 在编译阶段将分类中的方法、属性等编译到一个数据结构category_t
  • 将分类中的方法、属性等合并到一个大数组中去,而后参加编译的分类就会在数组前面
  • 将合并后的分类数据插入到原有数据的前面

故而当分类中的方法与原始类中方法重名的时候,会先去调用分类中实现的方法。

扩展

objc 复制代码
@interface Person ()

@property (nonatomic, assign) NSInteger age;  // 私有属性

- (BOOL)validateAge;  // 私有方法声明

@end

这里我们将一个扩展直接使用Clang转化位cpp文件,我们可以看到其直接被存储到了成员变量表中,同时方法也直接被添加到了metholist中:

故而扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对系统的类使用扩展。

扩展和分类的区别

类别、分类

  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加了成员属性也无法取到
    • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写settergetter方法
  • 分类中用@property 定义变量,只会生成变量的settergetter方法的声明不能生成方法实现和带下划线的成员变量

扩展

  • 可以说成是特殊的分类,也可称作匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法

关联对象

这里我们来讲解一下如何通过runtime来给分类添加属性,这里主要分为两部分:

  • 通过objc_setAssociatedObject设值流程
  • 通过objc_getAssociatedObject取值流程

流程如上所示

我们先来看看取值流程:

objc 复制代码
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
  • 参数一:要关联的对象,即为谁添加关联属性
  • 参数二:标识符,方便下次查找
  • 参数三:value
  • 参数四:属性的策略,即nonatomic、atomic、assign等,下面展示一下所有关联对象的属性类型:

下面我们来看看objc_setAssociatedObject的源码实现:

下面我们进入_object_set_associative_reference源码实现来看看:

objc 复制代码
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;
		
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
		//object封装成一个数组结构类型,类型为DisguisedPtr
    DisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用
    // 包装一下 policy - value
  	ObjcAssociation association{policy, value};
		
    // retain the new value (if any) outside the lock.
    association.acquireValue();//根据策略类型进行处理

    bool isFirstAssociation = false;
    {
        //初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
        AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
        AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回结构为一个类对
            if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
                /* 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 {//如果传的是空值,则移除关联,相当于移除
            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();

    // release the old value (outside of the lock).
    association.releaseHeldValue();//释放
}

我们来看看这段源码的实现过程:

  • 首先检查对象所属类是否禁止关联对象(系统类就不可以),若禁止则直接触发崩溃
  • 创建一个全局管理关联对象的AssociationsManager管理类,并获取唯一的全局静态哈希Map:AssociationsHashMap
  • value是否存在:
  • 若存在,通过try_emplace方法,创建一个空的ObjectAssociationMap去取查询键值对
  • 如果发现没有这个 key 就插入一个空的 BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象即置isa指针的has_assoc属性为true
  • 用当前policy 和 value组成了一个ObjcAssociation替换原来BucketT 中的空
  • 标记一下 ObjectAssociationMap 的第一次为 false

AssociationsManager

我们先来看看其源码实现

这里我们可以看到AssociationsHashMap从静态变量中取出,所以全场唯一

下面我们来看看这AssociationsHashMap以及ObjectAssociationMap的定义

这里先说一下DenseMap,这个东西时LLVM实现的高性能哈希表,支持快速插入、查找、删除(笔者具体也不会)

  • 先来看看ObjectAssociationMap,他对应的是一个对象的关联属性集合,通过健快速定位到具体的ObjcAssociation

​ 这里展示一下该结构体内部包含的内容:关联值的引用计数策略与实际值

  • 再来看看AssociationsHashMap,这是一个全局管理所有对象的关联属性的集合,这里键为伪装指针(DisguisedPtr ,值为该对象关联属性表ObjectAssociationMap

这里附一张图来讲解这几个表之间的关系

下面来说一下这几个map之间的联系与不同:

  • AssociationsManager可以有很多个,但是AssociationsHashMap类型的map只能有一个,是通过AssociationsManager来获取的
  • 这个map中有很多个ObjectAssociationMap类型的map,在上文中的讲解中,我们可以明白每个对象都有一个ObjcAssociation,所以每个对象都会有一个自己的ObjectAssociationMap类型的map

key的几种用法

  • 使用的get方法的@selector作为key
objc 复制代码
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
  • 使用指针的地址作为key
objc 复制代码
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
  • 使用static作为key
objc 复制代码
static char MyKey;

objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
  • 使用属性名作为key
objc 复制代码
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

流程

  • 设置关联对象:

    • 调用objc_setAssociatedObject

    • AssociationsManager查找或创建与目标对象相关的ObjectAssociationMap

    • ObjectAssociationMap中查找或创建对应的 ObjcAssociation

    • 将关联值和存储策略设置到 ObjcAssociation 中。

  • 获取关联对象:

    • 调用objc_setAssociatedObject

    • AssociationsManager查找与目标对象相关的ObjectAssociationMap

    • ObjectAssociationMap中查找对应的 ObjcAssociation

    • 返回ObjcAssociation中存储的关联值

  • 移除关联对象:

    • 调用 objc_removeAssociatedObjectsobjc_setAssociatedObject 设置为 nil。
    • AssociationsManager 查找与目标对象相关的ObjectAssociationMap
    • ObjectAssociationMap 中移除对应的 ObjcAssociation
    • 如果ObjectAssociationMap为空,可能会移除整个映射以释放资源。

这个流程其实也就是上文中_object_set_associative_reference的流程,笔者认为这样理解更好一些,下面再附一张图帮助理解

总结

关联对象就是一个二层哈希的处理,存取的时候都是两层处理,类似于二维数组:

相关推荐
Digitally43 分钟前
如何用4 种可靠的方法更换 iPhone(2025 年指南)
ios·iphone
lilye661 小时前
精益数据分析(103/126):免费移动应用的下载量、成本优化与案例解析
数据挖掘·数据分析
XiaoQiong.Zhang1 小时前
简历模板3——数据挖掘工程师5年经验
大数据·人工智能·机器学习·数据挖掘
9765033354 小时前
iOS 审核 cocos 4.3a【苹果机审的“分层阈值”设计】
flutter·游戏·unity·ios
I烟雨云渊T4 小时前
iOS Alamofire库的使用
ios
程序员老刘·4 小时前
iOS 26 beta1 真机无法执行hot reload
flutter·ios·跨平台开发·客户端开发
EndingCoder4 小时前
React Native 构建与打包发布(iOS + Android)
android·react native·ios
程序员小刘5 小时前
HarmonyOS 5鸿蒙多端编译实战:从Android/iOS到HarmonyOS 5 的跨端迁移指南详
android·ios·华为·harmonyos
I烟雨云渊T5 小时前
iOS swiftUI的实用举例
ios·swiftui·swift
XiaoQiong.Zhang6 小时前
简历模板2——数据挖掘工程师5年经验
人工智能·数据挖掘