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

你们好,我是叶孤城,和我很是尊敬的大神同名。前一篇文章中,咱们知道了isa的存在、做用,以及类的底层本质是一个objc_object的结构体。可是也带出了一个新的词儿—— 元类。今儿个咱们就谈谈元类,上一篇文章中,那个经典的类结构图是怎么画出来的。markdown

没错,就是上面这幅图。app

元类

解释元类以前,先说一个咱们熟悉的不能再熟悉的东西: 对象和类。 As we know , 对象是由类产生(准确的说:对象由类对象实例化),咱们前面说过:类也是一个objc_object,叫类对象。那么 类对象 同理也是由一个 "类" 产生,那么这个"类"就是 元类spa

当咱们再回顾这条线: obj -> LYPerson(类对象) ->LYPerson(元类) -> NSObject(根元类) -> NSObject(根根元类) 这就理解了为何有元类,元类是干什么的,其实就是产生类对象的类。3d

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的生命周期内,内存中只存在一份。咱们用两种方式作个实验:code

第一种:分别打印类对象的地址

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是我代码里的断点处本身定义的orm

  • // 查看变量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验证上面的class一、class二、class3是同一个类对象

结论:内存地址+内容都是LYPerson,OK,没有疑问了,类对象只有一个。

好,问题来了

元类在内存中是否是也只有一个?

类对象只有一个,类对象的类元类确定也只有一个。

NSObject在内存中也有一个?

LYPerson 和 LYAnimal 均是继承NSObject的自定义类,class一、class2分别是他们的元类,class十一、class22则分别是他们的根元类,也就是他们元类的类,都是NSOBject,内存地址也是同样的,

根元类NSObject的元类的元类的......是什么样

这个咱们使用LLDB类查看,OC代码每次只能运行一次,咱们的LLDB能够尽情的“追根溯源”

咱们从根元类开始看

一、根元类NSObject 后面的根根元类仍是NSObject ,内存地址同样;

二、根根元类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

相关文章
相关标签/搜索