底层研究 - 类的底层探索(中)

2022-06-15  本文已影响0人  Jacky_夜火

前言

底层研究 - 类的底层探索(上)中我们已经探索得知了类对象本质为objc_class结构体,同时类中的信息都存储在 class_rw_t 和 class_ro_t 中,那么接下来的重点就是探索下 class_rw_t 和class_ro_t以及使用runtime来获取类的基本信息。

1、clean memory & dirty memory

在WWDC 2020有一段介绍提到了关于class_rw_t 和 class_ro_t 之间的关系,推荐观看,十分牛掰!!o( ̄▽ ̄)d

Advancements in the Objective-C runtime

接下来我们结合视频和源码进行探索下,首先我们要先知道2个概念:

clean memory 是指加载后不会发生改变的内存。它可以进行移除来节省更多的内存空间,需要时再从磁盘加载。
class_ro_t就是属于 clean memory

dirty memory是指在运行时会发生改变的内存。当类开始使用时,系统会在运行时为它分配一块额外的内存空间,也就是dirty memory,只要进程在运行,它就会一直存在,因此使用代价很高。

2、 ro & rw & rwe

ro,也就是readonly,class_ro_t 是在编译的时候生成的。当类在编译的时候,类的属性,实例⽅法,协议这些内容就存在class_ro_t这个结构体⾥⾯了,这是⼀块clean memory,不允许被修改,但可以在不使用的时候被删除,需要的时候再从磁盘加载。

class_ro_t

rw,即readwrite,class_rw_t是在运⾏的时候⽣成的。当一个类第一次被使用时,rumtime会为其分配额外的内存,即class_rw_t ,它会先将class_ro_t的内容 剪切 到class_rw_t中存储,整个内存中其实只有一份

通过 First SubclassNext Sibling Class 指针实现了把类连接成一个树状结构,这就决定了runtime能够遍历当前使用的所有类

class_rw_t

可以发现Methods、Properties、Protocols不仅存在于ro,也存在了rw中,这是因为在运行时它们是可以发生改变的,比如通过category添加方法或者通过runtime的api动态添加它们,系统需要去跟踪这些内容。

rw拥有Methods、Properties、Protocols

但按这种做法,会导致占用相当多的内存,那怎么取缩小这些结构呢?

在对bits的源码探索中,我们发现了 rw 提供三个方法method()、properties()、protocols()来分别返回方法,属性和协议,也就是说rw并没有直接存储这三者,而是存在于一个ro_or_rw_ext,因为rw属于dirty memory,使用开销大,因为把一些类的信息分离出来,能减小开销。

method()、properties()、protocols()

class_rw_ext_t可以减少内存的消耗。苹果在wwdc2020⾥⾯说过,只有⼤约10%左右的类需要动态修改。所以只有10%左右的类⾥⾯需要⽣成class_rw_ext_t这个结构体。这样的话,可以节约很⼤⼀部分内存。对于动态修改的类可以通过class_rw_t结构体中提供的ext()方法获取class_rw_ext_t。

内存结构之class_rw_ext_t

我们通过源码也可以发现,在methods方法中,也是先判断的是否存在rwe,有则从rwe获取方法,无则直接从ro中获取。

rwe的使用

那么 class_rw_ext_t 的⽣成的条件是什么呢?

  1. ⽤过runtime的Api进⾏动态修改的时候。
  2. 有分类的时候,且分类和本类都为⾮懒加载类的时候。实现了+load⽅法即为⾮懒加载类。

因此我们也能得到一个rw,ro,rwe的具体关系图


rw,ro,rwe的具体关系图

3、runtime的基本使用

-(void)class_copyIvarList:(Class)pClass {
    unsigned int  outCount = 0;
    Ivar *ivars = class_copyIvarList(pClass, &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        const char *cName =  ivar_getName(ivar);
        const char *cType = ivar_getTypeEncoding(ivar);
        NSLog(@"name = %s type = %s",cName,cType);
    }
    free(ivars);
}
-(void)class_copyPropertyList:(Class)pClass {
    unsigned int outCount = 0;
    objc_property_t *perperties = class_copyPropertyList(pClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = perperties[i];
        const char *cName = property_getName(property);
        const char *cType = property_getAttributes(property);
        NSLog(@"name = %s type = %s",cName,cType);
    }
    free(perperties);
}
-(void)class_copyMethodList:(Class)pClass {
    unsigned int outCount = 0;
    Method *methods = class_copyMethodList(pClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        Method method = methods[i];
        NSString *name = NSStringFromSelector(method_getName(method));
        const char *cType = method_getTypeEncoding(method);
        NSLog(@"name = %@ type = %s",name,cType);
    }
    free(methods);
}
-(void)class_replaceMethod{
     //参数:1.类Class 2.⽅法名SEL 3.⽅法的实现IMP 4.⽅法参数描述
     //返回:BOOL
    BOOL result = class_replaceMethod([self class], @selector(method1), [self
    methodForSelector:@selector(method2)], NULL);
    NSLog(@"class_replaceMethod:%d",result);
}

-(void)method1{
    NSLog(@"method1");
}

-(void)method2{
    NSLog(@"method2");
}

+(void)load{
    NSString *className = NSStringFromClass(self.class);
    NSLog(@"classname %@", className);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = object_getClass((id)self);
 
        SEL originalSelector = @selector(systemMethod_PrintLog);
        SEL swizzledSelector = @selector(ll_imageName);
 
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        //在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO
        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

稍微讲解下:

  1. 如果该Class不存在指定SEL,则class_replaceMethod的作用就和class_addMethod一样;
  2. 如果该Class存在指定的SEL,则class_replaceMethod的作用就和method_setImplementation一样。

4、总结

  1. clean memory是指加载后不会发生改变的内存
  2. dirty memory是指在运行时会发生改变的内存
  3. ro、rw、rwe具体流程
    (1) 当编译的时候,类会自动生成ro
    (2) 当类被使用时,会生成rw,并把ro 剪切 到rw中
    (3) 当类被修改时,rw内会新增rwe,把允许修改的内容转移到rwe,读取时优先读取rwe的内容
    (4) 内存不足时,ro 可以在不使用的时候被移除
  4. 常用的runtime方法:
    (1) 成员变量:class_copyIvarList、ivar_getName、ivar_getTypeEncoding
    (2) 属性:class_copyPropertyList、property_getName、property_getAttributes
    (3) 方法:class_copyMethodList、method_getTypeEncoding、class_replaceMethod、class_addMethod、method_exchangeImplementations
上一篇下一篇

猜你喜欢

热点阅读