对象的实例方法和成员变量是存储的类对象的结构体class_rw_t中,在class_rw_t中properties里没有成员变量,那么成员变量储存在哪里呢?
实例变量储存位置
在Apple官方WWDC视频中有介绍类的运行时数据变化
- 首先class包括Metaclass元类,Superclass超类,Method cache方法缓存,还有一个指向更多数据的指针,存储额外信息的地方,叫做class_ro_t(ro表示只读)
- class_ro_t包括像类名称,方法,协议和实例变量的信息,swift类和OC类共享这一基础结构
- 当类第一次从磁盘加载到内存时,一经使用,就会发生变化,class_ro_t 是clean memory (指创建后不会更改的内存),class_rw_t是dirty memory(进程运行时会发生改变的内存),类结构一经使用就变成了dirty memory,因为运行时会向它写入新的数据,例如,创建一个新的方法缓存并从类中指向它。dirty memory 比 clean memory昂贵的多,只要进程在运行,dirty memory就一直存在,而clean memory可以从系统中移除,当你需要的时候可以从磁盘中重新加载。dirty memory是数据分为两部分的主要原因,可以保持clean memory中的数据越多越好。通过分离出那些永远不可能更改的数据,可以把大部分数据储存为clean memory。
- 运行时需要追踪更多类的信息,所以当一个类首次被使用,运行时会为它分配额外的储存空间classs_rw_t用于读取-编写数据,在这个数据结构中,存储了只有运行时才会生成的新信息,例如,所有的类都会链接成一个树状结构,这是通过使用class_rw_t中First Subclass和Next Sibling Class指针实现的,这允许运行时遍历当前使用的所有的类,这会使方法缓存失效。
-
但为什么方法和属性也在ro数据中时,class_rw_t也存在方法和属性,是因为他们在运行时可以被改变,当category被加载的时候,它可以向类添加新的方法,也可以通过运行时的API动态添加,因为class_ro_t是只读的,所以需要在class_rw_t中去追踪。那这样的结果就是会使用更多的内存,在任何给定的设备都有很多类在使用,Apple官方在一些iPhone设备上测定,大约30M字节这些class_rw_t,那么该怎么改善呢?
-
因为在读取-编写时,需要这些数据,但是通过检查实际设备这些数据的使用情况,Apple官方发现大约10%的类真正改变了他们的方法,所以拆掉那些平时不用的类,class_rw_t就缩小了将近一半。比如Demangled Name这个swift类只有在询问oc内容时才会用到。
-
对于那些确实需要额外信息的类,可以分配这些扩展记录中的一个,并把它划到其他类中使用,大约90%到类都不需要这些扩展信息,可以节省大约14M的内存用于其他用途,我们在Mac上可以通过heap命令,这个命令可以查看正在运行的进程所使用的堆内存
- 通过上图可以看出,有690个需要class_rw_t,但是只有143是必须要使用扩展的,这样可以清楚的计算出通过这个改变节省的内存,大约节省了1M字节的四分之一,这还只是对于Mail app来讲,如果在系统范围内进行推广,那么节省的内存时非常可观的
-
从很多类中获取数据的代码必须同时处理有扩展和没有扩展的类,这些运行时都会做到,而且通过这个改变更加节省内存。之所以这样,是因为读取这些结构的代码,都在运行时并且同步更新。
了解这些背景之后,那么我们就来探索一下class_ro_t中的内容。先找到对应的class_rw_t
得到了一个ivar_list_t继承自entsize_list_tt也是一个容器
为什么成员变量要放在类里面而成员变量的值要放在实例对象里面呢?
类的本质是一个结构体,相当于一个模板,就有方法成员变量属性等等,在创建实例对象的时候,值是不一样的,所以值要放在实例对象里面。
类方法储存在哪里呢?
先找到掩码0x00007ffffffffff8ULL,通过地址位移获取我们的元类内存地址,用类对象的isa指针地址 & 掩码获得元类的地址
从图中在元类方法中的方法列表中找到了类方法。
为什么要设计元类?
- 为了复用消息机制。objc_msgSend(id,sel)
- 在OC的世界里一切皆对象(借鉴于Smalltalk),metaclass的设计就是要为满足这一点。
- 在OC中Class也是一种对象,它对应的类就是metaclass,metaclass也是一种对象,它的类是root metaclass,在往上根元类(root metaclass)指向自己,形成了一个闭环,一个完备的设计。
- 如果不用metaClass,会将消息传递复杂化,要去判断消息接受者是类对象还是实例对象,sel是类方法还是实例方法。
- 元类对象就去储存类方法,类对象就储存实例方法,实例对象就去储存实例变量的值,实行单一职责,提高了消息发送的效率,也更容易维护。
获取类的属性和成员变量
//获取类的属性
- (void)tt_copy_propertyList:(Class)tclass{
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(tclass, &outCount);
for (int i = 0; i<outCount; i++) {
objc_property_t property = properties[I];
const char *cName = property_getName(property);
const char *cType = property_getAttributes(property);
NSLog(@"%u name = %s type = %s",outCount,cName,cType);
}
free(properties);
}
//获取类的成员变量
- (void)tt_copy_ivars:(Class)tclass{
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(tclass, &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(@"%u name = %s type = %s",outCount,cName,cType);
}
free(ivars);
}
//获取类的方法
- (void)tt_copy_methodList:(Class)tclass{
unsigned int outCount = 0;
Method *methods = class_copyMethodList(tclass, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[I];
const char *cName = method_getName(method);
const char *cType = method_getTypeEncoding(method);
NSLog(@"%u name = %s type = %s",outCount,cName,cType);
}
free(methods);
}
//name = test type = v16@0:8
v返回值类型void
16 v8个长度,消息接收者8个长度
- @:固定写法,@消息接收者,:方法名
// name = setName: type = v24@0:8@16
24 v8个字节,@0:8占8个字节@是一个NSString的对象参数从第16个字节开始占用8字节,共24
返回值类型
- c char
- I int
- s short
- l long 咋爱64位处理器上也是按照32位处理
- q long long
- C unsigned char
- I unsigned int
- S unsigned short
- L unsigned long
- Q unsigned long long
- f float
- d double
- B 代表C++中的bool或者C99的_Bool
- v void
- '*' char *
- @ 代表对象类型
- '#' 代表类对象(class)
- : 代表方法selector(SEL)
- [array type]代表array
- {name=type....}代表结构体
- (name=type...)代表union
- bnum A bit field of num bits
- ^type A pointer of type
- ? An unknown type
T类型,V_name成员变量
- R readOnly
- C copy
- & retain
- N nonatomic
- G<name> The property defines a custom getter selector name.The name follows the G(for example,GcustomGetter)
- S <name> The property defines a custom setter selector name.
- D @dynamic
- W __weak
- P The property is eligible for garbage collection
- t<encodeing>Specifies the type using ole-style encoding
-(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));
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));
class_getClassMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>)
IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethod));
NSLog(@"%p - %p - %p - %p",imp1,imp2,imp3,imp4);
//isa---
//isa --
}
Method method3 = class_getInstanceMethod(pClass, @selector(classMethod));获取元类方法通过class_getInstanceMethod方法也可以获取到,这是为什么呢?
发现class_getClassMethod内部其实也是返回了class_getInstanceMethod方法,所以通过class_getInstanceMethod也可获取到,所以进一步体现了元类的设计不仅仅是为了存储类方法,更是为了消息的复用。
获取方法实现
-(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);
}
//打印结果:
//0x1007df700 - 0x1007df700 - 0x1007df700 - 0x1007df700
从打印结果看出,从类里面获取实例方法实现可以找到,从元类获取实例方法的实现也可以找到,从元类里面去获取类方法实现也可以找到,在类里面获取类方法实现也可以找到。
//寻找方法实现的实现,当找不到的时候,会进行消息转发,所以上述代码找不到对应的方法实现的时候,返回的内存地址是一样的。
__attribute__((flatten))
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
总结
- 成员变量储存在类对象的class_rw_t结构体中的class_ro_t中,类方法储存在元类中,而设计元类也是为了复用消息机制,因为元类储存类方法,类对象储存实例方法和成员变量,实例对象储存成员变量的值,这样单一职责,提高了消息服用的效率,减少了消息传递的复杂性。
- class_rw_t是dirty memory,class_ro_t是clean memory,因为类一旦使用就需要动态修改,需要在class_rw_t中去追踪更多的信息,就会使用更多的内存,为了节省内存使用,拆掉平时不用的90%的类,增加了class_rw_ext_t,这里储存需要更改的必要扩展。
- class_ro_t必要可以从内存中移除,需要的时候再从磁盘中重新开辟。class_rw_t从类使用时创建,一直存在,直到进程销毁。
- class_rw_ext_t可以减少内存的消耗。苹果在wwdc2020里面说过,只有大约10%左右的类需要动 态修改。所以只有10%左右的类里面需要生成class_rw_ext_t这个结构体。这样的话,可以节约很 大一部分内存。
- class_rw_ext_t生成的条件:
第一:用过runtime的Api进行动态修改的时候。 第二:有分类的时候,且分类和本类都为非懒加载类的时候。实现了+load方法即为非懒加载类。