大家好,我是叶孤城,和我非常尊敬的大神同名。前一篇文章中,我们知道了isa的存在、作用,以及类的底层本质是一个objc_object的结构体。但是也带出了一个新的词儿—— 元类。今儿个我们就谈谈元类,上一篇文章中,那个经典的类结构图是怎么画出来的。
没错,就是上面这幅图。
元类
解释元类之前,先说一个我们熟悉的不能再熟悉的东西: 对象和类。 As we know , 对象是由类产生(准确的说:对象由类对象实例化),我们前面说过:类也是一个objc_object,叫类对象。那么 类对象 同理也是由一个 "类" 产生,那么这个"类"就是 元类 。
当我们再回顾这条线: obj -> LYPerson(类对象) ->LYPerson(元类) -> NSObject(根元类) -> NSObject(根根元类) 这就理解了为什么有元类,元类是干什么的,其实就是产生类对象的类。
void lgTestNSObject(){
// Student实例对象
Student *object1 = [[Student alloc] init];
// Student类
Class class1 = object_getClass(object1);
// Student元类
Class metaClass = object_getClass(class1);
// NSObject 根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象 \n%p 类 %@\n%p 元类 %@\n%p 根元类 %@\n%p 根根元类 %@",object1,class1,NSStringFromClass(class1),metaClass,NSStringFromClass(metaClass),rootMetaClass,NSStringFromClass(rootMetaClass),rootRootMetaClass,NSStringFromClass(rootRootMetaClass));
NSLog(@"=> %p",object_getClass(rootRootMetaClass));
}
总结:
- 对象 的类是 类(对象) ;
- 类(对象) 的类是 元类 ;和类同名
- 元类 的类是 根元类 NSObject;
- 根元类 的类是 自己 ,还是NSObject。
以下如有说到类对象,其实就是类。
类对象
我们开发中,一个类可以alloc N个obj,类对象不是,在app的生命周期内,内存中只存在一份。我们用两种方式做个实验:
第一种:分别打印类对象的地址
void lgTestClassNum(){
Class class1 = [LYPerson class];
Class class2 = [LYPerson alloc].class;
Class class3 = object_getClass([LYPerson alloc]);
NSLog(@"\n[LGPerson class]\t\t\t\t\t%p-\n[LGPerson alloc].class\t\t\t\t%p-\nobject_getClass([LGPerson alloc])\t%p-\n",class1,class2,class3);
}
第二种:使用LLDB查看
扩展:接下来所会用到的LLDB调试命令
演示的object、class是我代码里的断点处自己定义的
-
// 查看变量object的内存地址,并产生一个lldb中的变量$0,代替object (lldb) p object // 这是一个对象 (Student *) $0 = 0x000000010061eb80
-
// 也是查看内容 (lldb) po object // 这是一个对象 <Student: 0x10061eb80> (lldb) po class // 这是一个Class类型
Student (lldb) po a // 这是一个int变量a 10
* ```
// 转16进制
(lldb) p/x object // object的内容是地址
(Student *) $2 = 0x000000010061eb80
(lldb) p/x 10 // 10是一个十进制的数
(int) $3 = 0x0000000a
// 转2进制
(lldb) p/t object
(Student *) $3 = 0b0000000000000000000000000000000100000000011000011110101110000000
-
// x 查看内存 (lldb) x object 0x100512ce0: 65 33 00 00 01 80 1d 00 00 00 00 00 00 00 00 00 e3.............. 0x100512cf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
-
// x/[d]gx 查看内存分布,d表示查看几块,一块是8字节 (lldb) x/gx object 0x100512ce0: 0x001d800100003365 (lldb) x/4gx object 0x100512ce0: 0x001d800100003365 0x0000000000000000 0x100512cf0: 0x0000000000000000 0x0000000000000000 (lldb) x/8gx object 0x100512ce0: 0x001d800100003365 0x0000000000000000 0x100512cf0: 0x0000000000000000 0x0000000000000000 0x100512d00: 0x0000000000000000 0x1000000010050b0e 0x100512d10: 0x0000000000000000 0x0000000000000000
-
// 我们不仅可以查看变量,也可以直接查看内存地址 (lldb) po object <Student: 0x10046dba0> // 根据获得的内存地址,查看内存分布 (lldb) x/4gx 0x10046dba0 0x10046dba0: 0x001d800100003365 0x0000000000000000 0x10046dbb0: 0x0000000000000000 0x0000000000000000
0x10046dba0就是首地址,内存分布也是从首地址开始分配
使用lldb验证上面的class1、class2、class3是同一个类对象
结论:内存地址+内容都是LYPerson,OK,没有疑问了,类对象只有一个。
好,问题来了
元类在内存中是不是也只有一个?
类对象只有一个,类对象的类元类肯定也只有一个。
NSObject在内存中也有一个?
LYPerson 和 LYAnimal 均是继承NSObject的自定义类,class1、class2分别是他们的元类,class11、class22则分别是他们的根元类,也就是他们元类的类,都是NSOBject,内存地址也是一样的,
根元类NSObject的元类的元类的......是什么样
这个我们使用LLDB类查看,OC代码每次只能运行一次,我们的LLDB可以尽情的“追根溯源”
我们从根元类开始看
1、根元类NSObject 后面的根根元类还是NSObject ,内存地址一样;
2、根根元类NSObject 后面的元类还是自己,内存地址一样;
所以我们知道:
- 根元类NSObject在内存中只有一份;
- 根元类NSObject的元类——根根元类NSObject自然也是、也只能有一份。
根据目前我们掌握的知识,我们做一个isa画像,对,还是前一篇文章末尾的那个图
自定义类的子类isa链是不是和自定义类一样?
前面我们一直在讲直接继承于NSObject的子类,如果LYPerson又派生了Student类呢?
同理,我们可以验证一番:
准备一个继承于LYPerson的子类Student
#import "LYPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface Student : LYPerson
@end
NS_ASSUME_NONNULL_END
还是上面的那个isa指向链图:
我们分析过runtime源码:isa是一个Class类型,都是Class是一个objc_class的结构体指针。
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA; // isa是从objc_object继承而来,所以objc_class与生自带isa
Class superclass; // 重点在这:这个就是标记继承于哪个类。Student : LYPerson : NSObject
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
.
.
.
.
}
所以Class的isa指向的类对象、元类、根元类都是objc_class;所以说,从类对象开始,不光有isa这条线,还有通常所认识的“继承”着一条主线。类就是个双主演的电影:isa和superClass。
所以这张图我们又变了一下,加上“继承”这条主线:用实线表示继承,因为NSOBject在内存中只有一份,所以我们只保留一个NSObject
这张图还是残缺的,没有NSObject。NSObject是根类,根类上面没的继承了,是nil
并且,NSObject类对象和他的元类不是同一个
NSObject的元类已经是根元类!,所以上面的图,我们整理一下,去掉标记的person、student,将NSObject元类合并,整理后如下图所示:
事情并没有结束,根元类NSObject是个objc_class,他的superClass指向了哪里?
根元类NSObject的superClass是什么?
看完图肯定知道他指向的是根类NSObject,此处假装不知道,我们来找一找。
祭上lldb
我们通过x/4gx
查看根元类的内存分布,第8到第16个字节是superclass
。superClass
就是NSObject类对象
,内存地址都是0x0000000100334140
所以,我们最终得出了这么一个类的isa和继承关系的走位图
问题:为什么lldb 输出的第二个内存块0x0000000100334140
是superClass?
首先 ,object_class是一个结构体,我们之前专门谈过结构体的内存对齐,我们知道,结构体的内存分配是按照成员顺序,superClass在结构体内的位置排在第二位,第一位是从objc_object继承的isa,isa占8个字节,superClass和isa一样,也占8个字节,所以,我们从第二个内存快
struct objc_class : objc_object {
// Class ISA; // 占8个字节, 0~7
Class superclass; // 占8个字节, 8 ~ 15
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
.
.
.
.
}
所以,红色区域是superClass