美文网首页
底层研究 - 类的底层探索(中)

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

作者: Jacky_夜火 | 来源:发表于2022-06-15 23:27 被阅读0次

    前言

    底层研究 - 类的底层探索(上)中我们已经探索得知了类对象本质为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

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

    • dirty memory

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

    2、 ro & rw & rwe

    • class_ro_t

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

    class_ro_t
    • class_rw_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

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

    稍微讲解下:

    • dispatch_once
      dyld虽然能够保证调用Class的load时是线程安全的,但还是推荐使用dispatch_once做保护,防止极端情况下load被显示强制调用时,重复交换(第一次交换成功,下次又换回来了...),造成逻辑混乱。

    • class_addMethod
      给指定Class添加一个SEL的实现(或者说是SEL和指定IMP的绑定),如果该SEL在父类中有实现,则会添加一个覆盖父类的方法,添加成功返回YES,SEL已经存在或添加失败返回NO。
      因为iOS Runtime消息传递机制的影响,只执行method_exchangeImplementations操作时可能会影响到父类的方法,这也是先使用class_addMethod的原因。

    • class_replaceMethod

    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

    相关文章

      网友评论

          本文标题:底层研究 - 类的底层探索(中)

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