类的底层探究(下)
一、objc_class 的内部结构
详情见:https://juejin.cn/post/7090843696953819172/#heading-0
二、ro ,rw ,rwe三个结构体的理解
1.class_ro_t
class_ro_t是在编译的时候生成的。当类在编译的时候,类的属性,实例方法,协议、成员变量这些内容就存 在class_ro_t这个结构体里面了,这是一块纯净的内存空间,不允许被修改。
2.class_rw_t
class_rw_t是在运行的时候生成的,类一经使用就会变成class_rw_t,它会先将class_ro_t的内容"拿"过去,然后再将当前类的分类的这些属性、方法、协议(成员变量不会拿过去)等拷⻉到class_rw_t里面。它是可读写的.
生成时机:
在编译期间,
class_ro_t
结构体就已经确定,objc_class
中的bits
的data部分存放着该结构体的地址。在runtime
运行之后,具体说来是在运行runtime
的realizeClass
方法时,会生成class_rw_t
结构体,该结构体包含了class_ro_t
,并且更新data部分,换成class_rw_t
结构体的地址。3.class_rw_ext_t
class_rw_ext_t可以减少内存的消耗。苹果在wwdc2020里面说过,只有大约10%左右的类需要动态修改。所以只有10%左右的类里面需要生成class_rw_ext_t这个结构体。这样的话,可以节约很 大一部分内存。
生成条件:
第一: 用过runtime的Api进行动态修改的时候。
第二: 有分类的时候,且分类和本类都为非懒加载类的时候。实现了+load方法即为非懒加载类。
三、苹果设计元类的目的
主要的目的是为了复用消息机制,其实在苹果的底层,压根没有区分类方法和实例方法,他们在底层实现上其实都是函数。
- 在OC中调用方法,其实是在给某个对象发送某条消息。 消息的发送在编译的时候编译器就会把方法转换为objc_msgSend这个函数。id objc_msgSend(id self, SEL op, ...) 这个函数有俩个隐式的参数:消息的接收者,消息的方法 名。通过这俩个参数就能去找到对应方法的实现。
- objc_msgSend函数就会通过第一个参数消息的接收者的isa指针,找到对应的类,如果我们是通过 实例对象调用方法,那么这个isa指针就会找到实例对象的类对象,如果是类对象,就会找到类对象的元类对象,然后再通过SEL方法名找到对应的imp,然后就能找到方法对应的实现
- 那如果没有元类的话,那这个objc_msgSend方法还得多加俩个参数,一个参数用来判断这个方法 到底是类方法还是实例方法。一个参数用来判断消息的接受者到底是类对象还是实例对象。
- 消息的发送,越快越好。那如果没有元类,在objc_msgSend内部就会有有很多的判断,就会影响 消息的发送效率。
- 元类的出现就解决了这个问题,让各类各司其职,实例对象就干存储属性值的事,类对象存储 实例方法列表,元类对象存储类方法列表,符合设计原则中的单一职责,而且忽略了对对象类型的 判断和方法类型的判断可以大大的提升消息发送的效率,并且在不同种类的方法走的都是同一套流 程,在之后的维护上也大大节约了成本。
- 所以这个元类的出现,最大的好处就是能够复用消息传递这套机制。不管你是什么类型的方法,都是同一套流程。
四、Runtime的API读取方法属性等
//获取类的成员变量 -(void)lg_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)lg_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)lg_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)methodTest:(Class)pClass { const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className); Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethod)); Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod)); Method method3 = class_getInstanceMethod(pClass, @selector(classMethod)); //元类中获取方法的函数一样,说明元类中的方法也是实例方法,在OC底层压根没有区分元类和类,说明元类创造的目的不是为了存放类方法 Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod)); NSLog(@"%p - %p - %p - %p",method1,method2,method3,method4); } -(void)impTest:(Class)pClass { const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className); IMP imp1 = class_getMethodImplementation(pClass, @selector(instanceMethod)); IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethod)); IMP imp3 = class_getMethodImplementation(pClass, @selector(classMethod)); IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethod)); NSLog(@"%p - %p - %p - %p",imp1,imp2,imp3,imp4); //isa--- //isa -- } [self lg_class_copyIvarList:LGPerson.class]; //获取类的属性 [self lg_class_copyPropertyList:LGPerson.class]; //获取类的方法 [self lg_class_copyMethodList:LGPerson.class]; [self methodTest:LGPerson.class]; [self impTest:LGPerson.class];
运行结果如下:
五、做个小补充(其实在之前的(上)中已经写了成员变量以及类方法的存储位置)
- 成员变量存放在类对象的class_ro_t结构体当中
- 类方法存在元类当中