在上一篇章类的原理分析(上)我们大致分析了类的基本结构,以及isa的走位图,本篇将延续上篇进行细致分析
准备工作:WWDC 2020 - 类优化相关视频,LLVM源码
一.类优化
通过上面的视频内容,我们大致做一下总结
类的内存图解
1.Clean Memory
是指加载后不会发生更改的内存,class_ro_t是属于Clean Memory的。
class_ro_t(只读):一个指向更多数据的指针,存储额外信息的结构class_ro_t,ro代表只读,存放了方法、协议、实例变量的信息
2.Dirty Memory
是指在进程运行时会发生更改的内存,类的结构一经使用就会变成Dirty Memory,因为运行时会向它写入数据,这里指的是class_rw_t。
Dirty Memory 是这个类被分成两部分的原因,可以保持类加载后不会发生更改的数据越多越好,通过分离永远不会更改的数据,可以把大量的类数据存储为Clean Memory
class_rw_t(读写): Methods、Properties、Protocols,当category被加载时,它可以向类中添加新方法,可以根据Method Swizzling方式修改,因为class_ro_t是只读,所以要把这些放在class_rw_t中。
这是在视频中截取的示意图
3.class_rw_t优化
通过上面的描述可以看出,class_rw_t是在类被使用的时候,运行时分配的内存,但是我们又发现其实和class_ro_t存储的信息是一样的,只不过一个只读,另一个读写,那么问题就来了
问题1:为什么方法,属性在class_ro_t中时,class_rw_t还要有方法,属性呢?
因为它们可以在运行时进行更改,当category被加载时,它可以向类中动态添加新的方法,通过Runtime API手动向类中添加属性和方法,而class_ro_t 是只读的,所以我们需要在class_rw_t中来跟踪这些东西。
问题2:class_rw_t结构在苹果手机中,占用很多的内存,那么如何去缩小这些结构呢?
视频中说到:我们在读取—编写部分需要这些东西,因为它们在运行时可以被修改,但是大约10%的类是需要修改它们的方法,而且只有在swift中才会使用这个demangledName字段,但是swift类并不需要这个字段,除非是访问它们Objective-C名称时才需要。
优化:因此就有了优化的手段,也就是剥离平时用不到的部分,把class_rw_t用到的部分拆分出来,在运行时,分配内存,所以就有了class_rw_ext_t
二.梳理总结
其实在类的原理分析(上)中我们基本上已经把类的基本结构都通过lldb的方式,摸得七七八八了,包括在重识alloc流程(下)中我们也通过clang编译的方式,也看过我们上层代码在底层的实现,包括一些属性的声明,成员变量等等,再结合今天前面给的官方提供的视频解释,基本就可以得出结论
-
成员变量是声明在类的{}中的
-
属性是用@property方式声明的,会自动生成getter和setter方法,并且在底层编译阶段会变成_方式的成员变量
-
以对象类型声明的(特殊的成员变量)
-
如果声明的对象不设置如何除atomic之外的属性,那么默认属性是strong,不会触发objc_setProperty,也就是内存偏移实现
-
copy修饰的属性使用objc_setProperty方式实现,其它属性使用内存偏移实现
-
苹果没有把所有的setter方法全部写在底层,因为如果底层需要维护,修改起来特别麻烦。搞了个适配器中间层,中间层的作用是供上层的setter调用,中间层对属性的修饰符进行判断走不同的流程,调用底层的方法实现。中间层的优点:底层变化上层不受影响,上层变化底层也不会受影响
我们在底层代码中可以看到_method_list_t中那些生成的方法,都有一串特殊的字符,如下
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[24];
} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
24,
{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},
{(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},
{(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},
{(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},
{(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
{(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},
{(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_},
{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_};
这些其实是函数的一种特殊表现形式,官方也给出了字符对应的信息Apple Documents地址,大家可自行查阅
此图是从其他大佬的文章中借鉴而来,本人比较懒就没有去画图了,哈哈,图源在此
三.探索isKindOfClass和isMemberOfClass
1.测试题
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import "LGTeacher.h"
void lgKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
lgKindofDemo();
}
return 0;
}
2.结果
2021-06-22 15:31:32.186957+0800 KCObjcBuild[21446:304632] re1 :1
re2 :0
re3 :0
re4 :0
2021-06-22 15:31:32.189482+0800 KCObjcBuild[21446:304632] re5 :1
re6 :1
re7 :1
re8 :1
Program ended with exit code: 0
3.分析
通过源码分析得出,当然其实参照上篇的isa走位图也能分析出来
//题1:[(id)[NSObject class] isKindOfClass:[NSObject class]]; 1
//cls =NSObject tcls = self->ISA() = NSObject tcls->getSuperclass() = nil
//NSObject 属于根类 根类的元类是NSObject根元类 根元类父类所以是自己NSObject
//题3:[(id)[LGPerson class] isKindOfClass:[LGPerson class]]; 0
//cls =LGPerson tcls = self->ISA() = (LGPerson元类) tcls->getSuperclass()=(LGPerson元类的父类)=父类的元类 =(NSObject元类)
//LGPerson 元类存在 暂时称呼为(LGPerson元类) 而元类的父类自然而然也存在应该就是NSObject(NSObject是LGPerson父类)
//通过代码分析类方法 isKindOfClass:本质就是当前类和当前类的元类对比,相同则是yes,不相同则从元类的父类继续找,不断寻找父类的元类进行对比,直到没有则为NO
//cls和当前类的元类开始对比,不断元类父类递进
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
//题5:[(id)[NSObject alloc] isKindOfClass:[NSObject class]]; 1
//tcls = [self class] NSObject类 cls也是NSObject类
//题6:[(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; 1
// tcls = [self class] LGPerson类 cls也是LGPerson类
//对象方法isKindOfClass: 本质是当前类和对象的类开始对比,相同则是yes,不相同则从对象的类的父类继续找,不断寻找父类的元类进行对比,直到没有则为NO
//cls和当前类开始对比,不断父类递进
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
//cls和当前类的元类对比
//BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 0
//BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; 0
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//[(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; 1
//[(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; 1
//cls和当前对象的类对比
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
PS:有不少地方没有描述清楚,后续会详细补充