iOS底层原理05:isa底层原理探索下

965 阅读5分钟

前言

通过上一篇文章iOS底层原理04:isa底层原理探索上的探索,我们对isa有了更清晰的认识,这篇文章将继续探索isa的走位。

什么是元类

首先我们定义一个person,并且在NSLog处打个断点调试。(以下调试如不做说明,都处于macOS环境下)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        Person *person = [Person alloc];
        NSLog(@"Hello, World!  %@",person);
    }
    return 0;
}
  • 开启lldb调试,调试的过程如下
  • 1、以16进制格式化打印person的4段内存情况:
(lldb) x/4gx person
0x1006c25a0: 0x001d8001000021c9 0x0000000000000000
0x1006c25b0: 0x75736956534e5b2d 0x6369506261546c61
  • 2、将person的isaISA_MASK与运算,并且打印类信息
#   define ISA_MASK        0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000021c9 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000021c8
(lldb) po 0x00000001000021c8
Person

通过上一篇文章的探索,我们很清楚,这一步的与运算已经获取了person类信息

  • 3、我们接着打印Person的内存情况:
(lldb) x/4gx 0x00000001000021c8 
0x1000021c8: 0x00000001000021a0 0x0000000100334140
0x1000021d8: 0x000000010032e410 0x0000801000000000
  • 4、将PersonisaISA_MASK 与运算,并且打印此时的类信息:
(lldb) p/x 0x00000001000021a0 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000021a0
(lldb) po 0x00000001000021a0
Person

  • 通过上面调试过程,我们产生了一个疑问:为什么p/x 0x001d8001000021c9 & 0x00007ffffffffff8ULLp/x 0x00000001000021a0 & 0x00007ffffffffff8ULL 中的类信息打印出来都是Person

  • 0x001d8001000021c9 是person对象的isa指针地址,其&后得到的结果是 创建person的类Person

  • 0x00000001000021a0是isa中获取的类信息所指的类isa的指针地址,即 Person类的类 的isa指针地址,在Apple中,我们简称Person类的类为 元类 所以,两个打印都是Person的根本原因就是因为元类导致的。

###元类的说明

  • 元类的定义创建都是由编译器自动完成

  • 元类 是类对象 的类,所有类方法、协议等的归属都存在元类

  • 5、我们继续重复上面的步骤:

(lldb) x/4gx 0x00000001000021a0 
0x1000021a0: 0x00000001003340f0 0x00000001003340f0
0x1000021b0: 0x000000010071fb80 0x0003e03100000007
(lldb) p/x 0x00000001003340f0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00000001003340f0
(lldb) po 0x00000001003340f0
NSObject

此时打印的类信息是NSObject。

  • 6、继续打印NSObject的内存情况:
(lldb) x/4gx 0x00000001003340f0 
0x1003340f0: 0x00000001003340f0 0x0000000100334140
0x100334100: 0x000000010062a050 0x0004e03100000007

此时的isa 0x00000001003340f0指针地址 已经和 NSObject指针地址一样了。

##总结 从上面的探索,我们可以的出结论:

  • 对象isa 指向 (也可称为类对象)。
  • isa 指向 元类
  • 元类isa 指向 根元类,即NSObject。
  • 根元类isa 指向 它自己
  • `对象 --> 类 --> 元类 --> NSobject, NSObject 指向自身。

NSobject在系统中存在几份?

从上面的探索中可以看出,最后的根元类是NSObject,这个NSObject 与我们系统中NSObject是同一个吗?下面我们通过两种方式来验证:

  • 1、lldb命令验证:

  • 我们接着上述的验证,打印系统NSObject的首地址-->打印NSObject内存情况-->获取isa与运算 从图中可以看出,上述步骤最后NSObject类的元类 也是NSObject,与上面的Person中的根元类(NSObject)的元类,是同一个,所以可以得出一个结论:内存中只存在一份根元类NSObject,根元类的元类是指向它自己

  • 2、通过代码验证:

//MARK:--- 分析类对象内存 存在个数
void testClassNum(){
    Class class1 = [Person class];
    Class class2 = [Person alloc].class;
    Class class3 = object_getClass([Person alloc]);
    NSLog(@"\n%p-\n%p-\n%p-", class1, class2, class3);
}

从打印结果中可以看出,打印的地址都是同一个,所以NSObject(根元类)在内存中只存在一份。

isa走位图

实线是 super_class 指针,虚线是 isa 指针。 ###isa指向的几点说明:

  • 1、实例对象(Instance of Subclass)isa 指向 类(class)

  • 2、类对象(class) isa 指向 元类(Meta class)

  • 3、元类(Meta class)isa 指向 根元类(Root metal class) 根元类(Root metal class)isa 指向它自己本身,形成闭环,这里的根元类就是NSObject

###superclass的指向也有以下几点说明: 类的继承:

  • 1、子类(subClass) 继承自 父类(superClass)
  • 2、父类(superClass) 继承自 根类(RootClass),此时的根类是指NSObject
  • 3、根类 继承自 nil,所以根类即NSObject可以理解为万物起源

元类的继承:

  • 1、子类的元类(metal SubClass) 继承自 父类的元类(metal SuperClass)
  • 2、父类的元类(metal SuperClass) 继承自 根元类(Root metal Class
  • 3、根元类(Root metal Class) 继承于 根类(Root class),此时的根类是指NSObject

以上内容就是对isa的探索过程,这下子终于清楚isa指针与类是怎么关联的,也清楚了isa的走位图的流程,isa,我懂你了!