OC底层原理探究之关联对象

·  阅读 961

前言

我们都很清楚本身分类使用@Property添加属性只会有setter和getter方法的声明没有对应的实现,也不会有对应的带下划线的成员变量存在,目前我们添加属性都是通过关联对象的方式即运用了运行时相关的API来实现的,下面我们来分析下它的实现原理。

关联对象添加

我们常写的分类添加关联对象的代码如下:

- (void)setCate_name:(NSString *)cate_name{
    /**
     1: 对象
     2: 标识符
     3: value
     4: 策略
     */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}
复制代码

我们进入objc_setAssociatedObject()方法内部,看下它的实现:

截屏2022-05-26 下午12.45.12.png

进入_object_set_associative_reference方法:

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<objc_object> disguised{(objc_object *)object};
    //对存储策略和value进行相关赋值存储在ObjcAssociation对象中
    ObjcAssociation association{policy, value};

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

    association.acquireValue();

    bool isFirstAssociation = false;

    {
        //构造函数 AssociationsManager()   { AssociationsManagerLock.lock(); }
        //析构函数 ~AssociationsManager()  { AssociationsManagerLock.unlock();}
        //下面这行代码调用AssociationsManager构造函数
        AssociationsManager manager;
        //这里AssociationsHashMap是个全局静态变量,相当于单例,获取全局唯一的哈希表
        AssociationsHashMap &associations(manager.get());
        //value存在
        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;
            auto result = refs.try_emplace(key, std::move(association));

            if (!result.second) {
                association.swap(result.first->second);
            }

        } else {
            //若value不存在,移除关联对象
            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();
}
复制代码

简单的地方我们直接就上面代码里注释说明,这里有涉及哈希表(AssociationsHashMap)存储查询的地方associations-(AssociationsHashMap).try_emplace(disguised, ObjectAssociationMap{}),我们看下:

截屏2022-05-30 下午1.44.12.png

这里我们首先看到BucketT *TheBucket创建了一个空的桶子,接着调用LookupBucketFor(Key, TheBucket)判断根据Key查询对应的BucketT是否存在,若存在,调用make_pair返回当前BucketT并返回false,表示未添加新的BucketT,这里的Key是外面传入的object指针包装的DisguisedPtr<objc_object> disguisedValue是外部传入的ObjectAssociationMap,我们看下LookupBucketFor方法:

截屏2022-05-30 下午2.01.02.png

这里LookupBucketFor调用了另一个方法LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket)

Xnip2022-05-30_14-41-58.jpg

这里其实就是哈希表的一个查询过程,查询返回结果后,就接着根据返回的结果来继续执行后续代码,我们第一次进来调用LookupBucketFor的时候,bucket肯定是空的,所以接着执行InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...),把当前创建的空桶子存入AssociationsHashMap中,此时注意BucketT是空的,我们调试下:

截屏2022-05-30 下午3.08.21.png

我们看下InsertIntoBucket实现:

截屏2022-05-30 下午3.09.49.png

我们看到这里一个关键函数InsertIntoBucketImpl(Key, Key, TheBucket)

Xnip2022-05-30_15-15-07.jpg

这里插入完成后,我们在InsertIntoBucket返回的TheBucket就看到它就赋值完成:

截屏2022-05-30 下午3.43.36.png

此时我们第一次调用associations.try_emplace调用结束,我们看refs_result.secondtrue,此时isFirstAssociation也为true,此时我们的policyvalue还没有存进去,他们目前存在assocition对象中,我们看下refs这里,此时都还是空:

截屏2022-05-30 下午3.53.31.png

下一步refs.try_emplace(key,std::move(association))存入association,这里刚好也解释了第一次associations-(AssociationsHashMap).try_emplace(disguised, ObjectAssociationMap{})bucket最终插入后并不包含相关的KeyValue,因为此时association并没有传入,我们看refs.try_emplace这里,此时传入的Key就是我们外部传入的Key(这里是const void *类型值为cate_name),而Keybucketfirstvaluesecond

截屏2022-05-30 下午4.17.08.png

我们看refs.try_emplace调用结束后,发现bucketKeyValue都已存入:

截屏2022-05-30 下午4.27.25.png

到这里,我们基本已经清楚了它的存储结构,大致是这样的,我们看张图:

截屏2022-05-30 下午4.59.33.png

关联对象设值流程

  1. 创建一个 AssociationsManager 管理类
  2. 获取唯一的全局静态哈希Map
  3. 判断是否插入的关联值是否存在:
    3.1 存在走第4步
    3.2 不存在:关联对象插入空流程
  4. 创建一个空的 ObjectAssociationMap 去取查询的键值对
  5. 如果发现没有这个key就插入一个空的BucketT进去,然后返回
  6. 标记对象存在关联对象
  7. 用当前修饰策略(policy)和值(value)组成了一个ObjcAssociation替换原来BucketT中的空
  8. 标记一下 ObjectAssociationMap的第一次为false

关联对象取值分析

我们上面分析的是objc_setAssociatedObject,接下来看下objc_getAssociatedObject(self, "cate_name"),我们进入底层源码看下它的具体实现:

_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        //创建一个 AssociationsManager管理类
        AssociationsManager manager;
        //获取唯一的全局静态AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        //根据 DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        //如果这个迭代查询器不是最后一个获取 :ObjectAssociationMap (这里有策略和value)
        if (i != associations.end()) {

            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            //找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
            if (j != refs.end()) {

                association = j->second;
                association.retainReturnedValue();
            }
        }
    }
    //返回value
    return association.autoreleaseReturnedValue();

}
复制代码
  • 创建一个 AssociationsManager 管理类
  • 获取唯一的全局静态哈希表AssociationsHashMap
  • 根据DisguisedPtr找到AssociationsHashMap中的 iterator迭代查询器
  • 如果这个迭代查询器不是最后一个 获取:ObjectAssociationMap(这里有policy和value)
  • 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 返回value

关联对象移除

关联对象的移除在dealloc方法中,我们看下它调用流程:dealloc——>_objc_rootDealloc——>rootDealloc

截屏2022-05-31 下午2.13.45.png

我们接着看object_dispose()——>objc_destructInstance

截屏2022-05-31 下午2.16.12.png

我们进入_object_remove_assocations,看下它的实现:

截屏2022-05-31 下午2.19.13.png

相关面试题分析

[self class]和[super class]的区别

[self class]本质是objc_msgSend,消息的接收者是self,方法编号:class[super class]本质是objc_msgSendSuper,消息的接收者也是self,方法编号:class;我们看个例子:

截屏2022-06-07 下午2.36.09.png

我们运行之后这里输出得到的都是LGTeacher,那么为什么呢?首先LGTeacher中是没有class这个方法的,该方法存在于NSObject中,我们可以进入class方法看下:

截屏2022-06-07 下午3.30.16.png

我们之前就已经知道方法调用本质是消息的发送,底层调用的objc_msgSend(),这里的class方法是有两个默认参数的id selfsel _cmd,只是被系统隐藏起来了,我们要清楚这里的self,就是传进来的实例对象,而object_getClass()内存实现是这样的:

截屏2022-06-07 下午3.34.57.png

obj——>getIsa()获取的就是当前类,因为我们之前已经讲过实例对象的isa指向当前类,所以获取的LGTeacher[super class]这里的super只是个关键字,跟前面self不同,self是形参名,我们汇编调试:

截屏2022-06-07 下午4.12.28.png

我们可以看到[super class]底层调用的objc_msgSendSuper2,我们源码中看下它:

截屏2022-06-07 下午4.24.28.png

这里我看注释就可以知道super使用的当前类,而不是它的父类,我们看下这里的objc_super的数据结构:

截屏2022-06-07 下午4.30.00.png

从上图中可以看出,这里recevier接收的是当前类的实例,最后一句注释我们可以知道[super class]其实只是保证了当前父类被作为第一个查询的类,所以到这里我们基本也就清楚了[super class]为什么跟[self class]输出同一个类,因为他们最终接收到的都是当前类的实例,通过实例对象找到当前类。这里objc_msgSendSuper2本质也是从objc_msgSuperSend()跳转过来的,我们在汇编代码可以看到:

ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//这里做了objc_msgSendSuper2的跳转
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper
复制代码
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改