iOS开发底层之类加载下 (关联对象) - 14

Posted iOS_developer_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之类加载下 (关联对象) - 14相关的知识,希望对你有一定的参考价值。


前言

  1. ro rw rwe的补充?
    ro (干净内存,只读) -》 rw (脏内存,昂贵,所以需要优化) -》 rwe 。
    之所以会有rw,是因为运行时功能,会修改内存,这时候因为Ro只读,不能修改,所以产生了rw, 用来去追踪, 如果在运行时操作了分类,动态添加方法, 会浪费很多内存,但有的类是不会通过运行时去进行分类,去动态添加方法,所以有了 rwe , 里面存放运行时添加的分类,属性,协议等。 对rw的一个extension。
  2. 本节主要讲的内容为分类的扩展

一、分类什么时候加载?

  1. 根据attachCategories方法,去反推,哪些地方调用了这个方法。
    最终推导出来只有attachToClass 方法调用。

二、attachCategories

项目新建好分类,并写上load方法, 在所有的这个方法处,打上断点,观察执行顺序。 多种情况说明

分类 + 类搭配加载

  1. 两个都有load方法: _read_images 懒加载类 -》 realizeClassWithoutSwift -》 load_categories_nolock -》 attachCategories

  2. 分类没有load方法, 主类有load方法
    _read_images -》 realizeClassWithoutSwift -》 methodizeClass -》 attachToClass ==没有走attachCategories 方法。 ==

  3. 分类有load 方法, 主类没有load方法
    _read_images -》 realizeClassWithoutSwift -》 methodizeClass -》 attachToClass ==没有走attachCategories 方法。 ==

  4. 两个都没有load方法
    直接到main函数了,说明方法都没有执行,走了懒加载流程了。

总结:4种情况:

  1. 非懒加载类 + 非懒加载类
    _getObjc2NonlazyClassList -》 readClass - 》 realizeClassWithoutSwift -》 methodizeClass -》 load_categories_nolock --》 attachToClass -》 attachCategories
  2. 懒加载类 + 非懒加载分类: 会迫使类成为非懒加载类提前加载数据。
  3. 懒加载类 + 懒加载分类: 消息第一次调用,才会加载数据。
  4. 非懒加载类 + 懒加载分类: read_image就开始加载数据。

三、 类扩展、分类 应用层

1. category:分类

  1. 专门用来给类添加新的方法。
  2. 不能给类添加成员属性, 添加了成员变量,也无法取到。
  3. 但我们可以通过runtime去给分类添加属性。
  4. 分类中用@perperty定义变量, 只会生成getter,setter的方法申明, 不能生成方法实现和带下划线的成员变量。

2. extension:扩展

  1. 特殊的分类,匿名分类。
  2. 可以添加成员属性,是私有变量
  3. 可以给类添加方法, 是私有方法

3 .关联对象 (AssociatedObject)

  1. 方法为 _object_set_associative_reference 。 源码如下。
/**
 关联对象 : 存储 object - cate_name -> 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;

    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强转成objc_object类型,得到DisguisedPtr<objc_object>类型的对象disguised
	// 或者是这样: DisguisedPtr<objc_object> disguised = (objc_object *)object
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};

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

    bool isFirstAssociation = false;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());

        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 {
            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();
}

源码解读: objc_setAssociatedObject需要传入4个参数,分别是被关联者,关联标记,关联对象的值和关联策略。
核心流程为: 代码中先创建了AssociationsManager对象manager,再获取全局的hashMap表,然后根据value进行处理

4. AssociationsHashMap

  1. 源码如下:
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();
    }
};

源码解读: AssociationsHashMap是通过manager.get()获取HashMap,它的源码是通过_mapStorage调用get()方法实现的,而_mapStorage是一个static类型,所以获取的这张表是唯一的,这个获取表的过程是个单例方法

总结

1. 设值过程

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

2. 取值过程

  1. HashMap
  2. 然后根据object获取ObjectAssociationMap bucket
  3. 再获取ObjectAssociationMap
  4. 然后获取ObjcAssociation bucket
  5. 再获取ObjcAssociation
  6. 最后根据policy返回value

以上是关于iOS开发底层之类加载下 (关联对象) - 14的主要内容,如果未能解决你的问题,请参考以下文章

iOS底层探索之类的加载:类的关联对象AssociatedObject

iOS底层探索之类的结构(上)

iOS开发底层之类加载(中) - 13

iOS开发底层之类加载(中) - 13

iOS底层探索之类的结构(下)

iOS开发底层之类加载上 - 12