源码地址
对象的本质
- 一个对象的本质是C语言的结构体,可以通过对 OC 源文件转到 C++ 源码得到
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp
- OC 对象,最低占用 16 个字节,如果小于16个字节,则强制分配16个字节的存储空间。而且整个对象的大小必然是16的倍数。内存对其也会对这此有影响。
- 结构体的最大占用空间,是最大元素的倍数。
sizeof 是在编译的时候,就已经根据类型替换成了对应类型的大小。和 getInstanceSize 不同。
//需要导入 #import <objc/runtime.h>
class_getInstanceSize([XXX class]) 这是获取某个类的大小,但实际上是某个类中所有成员变量的大小之和。也就是这个实例对象至少需要多少。
#import <malloc/malloc.h>
malloc_size((__bridge const void *)xxxx) 这是获取某个实例对象实际分配的内存大小。 malloc 是C语言中分配内存的句式。
至少需要多少,是它本身的内容决定的。但是实际分配大小是操作系统决定的。存在内存对其,所以一般分配的都比需要的大或相等。
- 其中 ISA 指针占用8个字节,剩下的空间会分配给后来的成员变量。如果是继承,则会继承父类的成员变量,但ISA只有一个指针。
OC对象的分类
主要有3类
instance 对象(实例对象)
- 通过 alloc 产生的对象,每次调用 alloc 都会产生新的 instance 对象。
- instance 对象在内存中存储的信息包括:ISA 指针及其他成员变量的具体值。不会有方法。
class 对象 (类对象)
- 一个类只有一个类对象,所以通过多种方式获取的都是同一个类对象。
- 类对象在内存中存储的信息包括:ISA指针、superclass指针、类的协议信息(protocol)、类的成员变量(ivar)、类的对象方法等等
- class 方法返回的一直都是类对象,无论嵌套调用多少次。
meta-class 对象(元类对象)
- 通过 object_getClass() 获取元类对象。 但是参数必须是类对象。 如果传递的是实例对象,则获取的是类对象。 如果传的是类对象,返回的才是元类对象。
- objectMetaClass是NSObject的meta-class对象(元类对象)
- 每个类在内存中有且只有一个meta-class对象
- 元类对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:isa指针、superclass指针、类的类方法信息
- class_isMetaClass(),可以用来判断是否为元类对象
ISA 指针指向哪里?
- instance 的 isa 指向 class 对象。
- class 的 isa 指向 meta-class 对象。
- meta-class 的 isa 指向 基类的 meta-class 。
第二张
- instance 的 isa 指向 class ,既实例对象的 isa 指针指向类对象。当实例对象,调用
对象方法
的时候,由于对象方法
不在 instance 对象里,所以通过 isa 找到 class ,最后找到对象方法
的实现进行调用。 - class 的 isa 指向 meta-class ,既类对象的 isa 指针指向元类对象。当类对象方法调用
类方法
的时候,通过 class 的 isa 指针找到元类对象(meta-class),最后找到类方法
的实现进行调用。 - 类对象中有
superClass
指针,这个指向父类对象。 如果一个实例对象调用了父类的对象方法、甚至是元类的对象方法,其实就是实例对象向父类对象发送消息,则就是先通过isa
指针找到该实例对象的类方法 class,然后通过类方法的superClass
来一层层找父类对象的对象方法。 - 元类对象中调用父类甚至是多层父类的
类方法
的时候,也是通过 isa 先找到当前的元类对象,然后通过superClass
找到对应父类的类方法
进行调用。 - 实例对象的
isa
指向对应类对象,而对应类对象的isa
指向元类对象。 - 类对象的
superClass
指向它的父类,也是类对象,以此类推,superClass
指向的都是类对象。
总结:
- instance 的 isa 指向 class ,既实例对象的 isa 指向类对象。
- class 的 isa 指向 meta-class,既类对象的 isa 指向元类对象。
- meta-class 的 isa 指向基类的 meta-class ,既元类对象的 isa 指向基类(顶层父类)的元类对象。
- class 的 superclass 指向父类的 class 。(实例对象没有 superClass 指针)如果没有父类,则superclass指针为nil。
- meta-class 的 superclass 指向父类的 meta-class 。
基类的元类(meta-class)的superclass 指向基类的 class
。 - instance 调用对象方法,先通过isa找到对象方法,因为对象方法都存在 class 中。 然后如果 class 中没有,则通过 class 的 superClass 去找,没找到则报错 。
- class 调用类方法(既 meta-class 中的存储的),方法不存在的话,就通过 superClass 去找父类中的类方法,如果一直找不到,最终会找该对象的基类的
对象方法
中找,如果还找不到,就报错。 - isa 通过一次 &ISA_MASK 进行一次位运算,才会得到真实的地址值。
OC 的类信息存放在哪里
- 对象方法、属性、成员变量、协议信息,存放在 class 对象中。
- 类方法,存放在 meta-class 对象中。
- 成员变量的具体值,存放在 instance 对象中。
调试相关
- 在 xcode 打断点的时候,可以通过 Debug -> debug workflow -> view memory 来查看对象的内存地址。
- LLDB 是 xcode 内建的调试器。
命令 | 作用 | 备注 |
---|---|---|
print/p | 打印 | |
po | 打印对象 | |
memory read 或者直接 x + 内存地址 | 按照一定格式读取内存地址 | 如:x/数量、格式、字节数 x/3xw 0x1010001 其中,x是16进制,f是浮点,d是10进制 ;b:byte 1字节,h:half word 2字节;w:word 4字节,g:giant word 8字节 |
memory write + 内存地址 + 数值 | 修改某个内存地址对应的数值 |
面试题
- 一个 NSObject 对象一共占用了多少内存空间?
答:
系统会分配16个字节给一个 NSObject 对象,可以通过 malloc_size 函数来获取。 但是 NSObject 对象内部只用了 8 个字节的空间(64位机器的环境下),可以通过 getInstanceSize 函数来获取。
-
ISA 指针指向哪里?
答:
instance 对象的 isa 指针指向对应的类对象。
类对象 class 的 isa 指针指向对应的元类对象。
元类对象 meta-class 的 isa 指针指向基类的元类对象。 -
OC 的类信息存放在哪里?
答:
类的成员变量值存放在 instance 实例中。
类的属性、成员变量、协议、对象方法存在对应的类对象中。
类的类方法存在在元类对象中。