美文网首页
iOS类的扩展和关联对象

iOS类的扩展和关联对象

作者: 似水流年_9ebe | 来源:发表于2021-07-22 14:09 被阅读0次

前言

iOS类的加载原理(上)以及iOS类的加载原理(下)这两篇文章比较全面的介绍了类的加载原理,这篇文章主要是对以上进行一些补充,并对类的扩展和关联对象进行分析。

分类加载的补充

methodList数据结构

methodList本身是array_t的
首先主类RoPerson的Load注释掉,RoPerson+RoA和RoPerson+RoB分类的Load开启,
运行项目,发现在load_categories_nolock这个函数断点卡住了,如图:

1
我们继续运行,在attachCategories这个函数停住,如图:
2
在这里我们分析下methodList,如图:
3
这是methodList的结构,这时有method_t,method_list_t。
在图2当中method_list_t存储的是指针,接下来我们打印下method_list_t的数据,如图:
4
这里的$1是数组结构,数组结构可以用下标的方示获取,但这里不是,而是用get方法获取是拿到的指址地址,这里的指针地址是指向method_list_t它是继续entsize_list_tt的一个结构体,通过分析,它是有get方法的,这里不再赘述。我把它的get方法截图:
5
从这里可以看出get方法调用了getOrEnd方法,这里是通过获取当前地址指针不断的通过位移this,再强制转换PointerModifier类型。
从上可以分析中,methodList存的是指针,而不是method_t的数据结构。

主类没有实现load,分类实现load,数据加载的问题

iOS类的加载原理(下)这篇文章有介绍过,主类没有实现load,分类实现load,它的流程是:
_read_image -> realizeClassWithoutSwift -> methodsizeClass -> attachToClass -> 没有走attachCategories
我们再测试一下,得到如下图:

6
从上图中可以看出这里调用了attachCategories函数。
现在是A分类和B分类的的load都是打开的,如果我们关闭其中一个分类的load方法,发现不调用attachCategories函数,我们再创建一个分类C,再把C的load方法打开,把A的分类关闭,发现也是调用了attachCategories函数。
从上分析可以得到,多个分类时,主类的load方法关闭,分类有一个以上的Load方法打开,就会调用attachCategories函数。
我们看下堆栈,如图:
7
Load_image会调用prepare_load_methods这个方法,准备load的方法,它里面调用了_getObjc2NonlazyCategoryList(获取非懒加载分类列表),同时也会调用realizeClassWithoutSwift这个函数。同时我们还可以发现,如果分类里面什么时候都没实现,不会进行加载的。

分类加载是否需要排序

我们先把C和D分类删除,从methods_list的分析来看,methods_list应该存的是RoA数组指针(会进行排序),RoB数组指针(会进行排序),主类的数组指针(会进行排序)(iOS类的加载原理(下)这里有介绍)。
我们在getMethodNoSuper_nolock这个函数的测试代码中打断点并运行项目,如图:

8
 auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)

这里就是遍历array_t,我们再来看下methods.beginLists(),如图:

9
从上分析分类加载是不需要排序

class_ro_t

  struct class_ro_t {
    uint32_t m_flags;
    uint32_t m_instanceStart;
    uint32_t m_instanceSize;
    uint32_t m_reserved;

    lldb::addr_t m_ivarLayout_ptr;
    lldb::addr_t m_name_ptr;
    lldb::addr_t m_baseMethods_ptr;
    lldb::addr_t m_baseProtocols_ptr;
    lldb::addr_t m_ivars_ptr;

    lldb::addr_t m_weakIvarLayout_ptr;
    lldb::addr_t m_baseProperties_ptr;

    std::string m_name;

    bool Read(Process *process, lldb::addr_t addr);
  };

以上是我们从llvm的源码中找到的class_ro_t的结构,
这里的bool Read(Process process, lldb::addr_t addr);这行代码读取当前的address,然后经过相关处理,然后再通过extractor读取到方法列表,经过分析,read函数是在Read_class_rowRead_objc_class调用,在* Read_objc_class(读取类)**对当前数据结构赋值(编译时期)。

类的扩展分析

我们先介绍类别,分类,类扩展的概念,如下:


10

贴下main.m代码

@interface RoStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)instanceMethod;
+ (void)classMethod;

@end

@interface RoStudent ()

@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end

@implementation RoStudent
- (void)instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)classMethod{
    NSLog(@"%s",__func__);
}


- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}
@end

其中

@interface RoStudent ()

@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end

就是为RoStudent类添加了一个类扩展。
然后我们通过clang命令翻译成C++文件。
打开这个C++文件,搜索ext_instanceMethod这个方法,如图:

11
这里找到了RoStudent添加的方法,还有属性(被注释),再次搜索,如图:
12
这里可以看到加载进去了,ext_instanceMethod会随着主类的加载而加载。
我们再为RoPerson添加一个类扩展(RoPerson_Ro.h文件名),代码如下:
@interface RoPerson ()

- (void)ext_instanceMethod;

+ (void)ext_classMethod;
@end

我们在realizeClassWithoutSwift这个函数打断点运行项目,如图:

13
然后执行如下图的命令:
14
我们找到了** ext_instanceMethod**这个方法。
类的扩展数据加载是做为类的一部分加载进来了。

关联对象

我们先看下图:


15

在分类A中,这两个警告,因为我们添加的两个属性导致,那么为什么会这样,我们接着分析。
我们先把RoA分类中的关联对象的代码贴出来,如下:

- (void)setCate_name:(NSString *)cate_name{
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}


- (void)setCate_age:(NSString *)cate_age{
    objc_setAssociatedObject(self, "cate_age", cate_age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_age{
    return objc_getAssociatedObject(self, "cate_age");
}

我们进入objc_setAssociatedObject这个函数的实现,如图:

16

我们再看下_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));

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

isFirstAssociation这时的代码块是这个函数的重点, 我们来分析下,它到底做了什么(关联对象的目的,是存储)。
我们先分析下它的参数void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t )

key是键的key。
value是键值。
policy缓存策略,retain或者copy。
object是被关联者。
DisguisedPtr<objc_object> disguised{(objc_object *)object};是ptr的处理,就是value的处理,包装成统一数据结构。
ObjcAssociation association{policy, value};进行了存储。
我们打个断点,并运行项目,开始调试。
断点执行到_object_set_associative_reference这个函数的184行代码,停住。
执行终端命令,如图:

16
说明是正确的,因为我们传的值就是Ro。
AssociationsManager manager;这个会自动执行构造函数和析构函数,源码如下:
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();
    }
};

在这代码块中间会进行锁上和打开。
AssociationsManagerLockspinlock_t,而** spinlock_t又是mutex_tt<LOCKDEBUG>就是互斥锁,这里是AssociationsHashMap这个哈希表的单例,因为AssociationsManager有一个static Storage _mapStorage;表局静态变量,AssociationsHashMap是全局唯一的。
我们打印下AssociationsHashMap数据结构,如图:

17
$3才是它的数据。
我们看下
try_emplace**这个函数的代码,如下:
 template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

Bucket在之前的catche的分析中有介绍,这里** Key就是disguised是个地址,LookupBucketFor是寻找Bucket。
** try_emplace
函数中找到Bucket返回,如果没找到,会添加一个新的Bucket(没有值),并返回。
auto &refs = refs_result.first->second;拿到refs_result.first的second的值。
auto result = refs.try_emplace(key, std::move(association));
这里并没有找到Bucket,因为Bucket还是一个空的筒子。
接着走到这里

if (isFirstAssociation)
        object->setHasAssociatedObjects();

如果isFirstAssociation为true,所以会调有这个函数setHasAssociatedObjects

 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);

                    }
                }
            }
        }

如果value不存在的话,会执行以上代码,清空这个数据。
关联对象会在objc_removeAssociatedObjects 这个函数进行移除,我们来分析下。
关联对象的生命周期跟着object一起。
objc_removeAssociatedObjects这个函数是在dealloc发起的,对象的生命周期释放的时候。

18 19 20 21 22

从以上几幅图中可能得到答案。

关联对象取值以及设值流程图:

23

结语

这篇文章我们做了对类的加载的一些补充,对类的扩展做了介绍了,同时分析了关联对象的原理,如果有错误或者遗漏,请指正。

相关文章

  • [iOS] 类扩展和关联对象

    本文主要是针对类的加载的扩展,探索下分类的底层实现原理。 1. 类扩展和分类介绍 1.1 category 类别、...

  • iOS类的扩展和关联对象

    前言 iOS类的加载原理(上)[https://www.jianshu.com/p/b2a845340192]以及...

  • iOS 类扩展&关联对象

    前言 前面几篇文章主要分析了类的加载过程,那么这篇文章主要分析分类中属性的存储,也就是大家常说的关联对象,以及类扩...

  • iOS 类的扩展&关联对象

    前言 在之前的文章中,我们已经分析了分类的本质和加载原理,不熟悉的同学可以查阅iOS-类的加载(下)[https:...

  • 类扩展和关联对象

    分类和类扩展 在OC类的加载[https://www.jianshu.com/p/0d728be5b598]中我们...

  • 类扩展&关联对象

    我们在前面的文章讲到类和分类的加载原理,今天我们来探索下类扩展和关联对象。 在这之前我们下来看看类扩展和分类的区别...

  • iOS底层学习:类的扩展和关联对象

    类的扩展和分类 category:分类、类别 给类增加方法 不能添加成员变量 可以使用runtime给分类添加属性...

  • iOS底层原理20:类扩展与关联对象底层原理探索

    在前面的文章中,我们分析了类和分类的本质和加载过程,本文主要来分析类扩展和关联对象 【面试题】类扩展与分类的区别 ...

  • iOS-OC底层14:关联对象和类扩展

    类扩展 1.可以说成是特殊的分类,也称作匿名分类分类的声明 2.可以给类添加成员属性3.可以给类添加方法。 总结:...

  • iOS类扩展 与 关联对象 底层原理

    本文的主要目的是针对类加载的一个扩展,主要讲类拓展和分类的底层实现原理 【面试题】类扩展 与 分类 的区别 1、c...

网友评论

      本文标题:iOS类的扩展和关联对象

      本文链接:https://www.haomeiwen.com/subject/awqlmltx.html