触摸iOS底层:类和isa(二)——类、元类、根元类和isa走位图

1,147 阅读8分钟

大家好,我是叶孤城,和我非常尊敬的大神同名。前一篇文章中,我们知道了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));
}

image.png

总结:

  1. 对象 的类是 类(对象)
  2. 类(对象) 的类是 元类 ;和类同名
  3. 元类 的类是 根元类 NSObject;
  4. 根元类 的类是 自己 ,还是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);
}

image.png

第二种:使用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个字节是superclasssuperClass就是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