源码地址

iOS runtime 源码

对象的本质

  • 一个对象的本质是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指向的都是类对象。

总结:

  1. instance 的 isa 指向 class ,既实例对象的 isa 指向类对象。
  2. class 的 isa 指向 meta-class,既类对象的 isa 指向元类对象。
  3. meta-class 的 isa 指向基类的 meta-class ,既元类对象的 isa 指向基类(顶层父类)的元类对象。
  4. class 的 superclass 指向父类的 class 。(实例对象没有 superClass 指针)如果没有父类,则superclass指针为nil。
  5. meta-class 的 superclass 指向父类的 meta-class 。基类的元类(meta-class)的superclass 指向基类的 class
  6. instance 调用对象方法,先通过isa找到对象方法,因为对象方法都存在 class 中。 然后如果 class 中没有,则通过 class 的 superClass 去找,没找到则报错 。
  7. class 调用类方法(既 meta-class 中的存储的),方法不存在的话,就通过 superClass 去找父类中的类方法,如果一直找不到,最终会找该对象的基类的对象方法中找,如果还找不到,就报错。
  8. 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 + 内存地址 + 数值 修改某个内存地址对应的数值

面试题

  1. 一个 NSObject 对象一共占用了多少内存空间?

答:
系统会分配16个字节给一个 NSObject 对象,可以通过 malloc_size 函数来获取。 但是 NSObject 对象内部只用了 8 个字节的空间(64位机器的环境下),可以通过 getInstanceSize 函数来获取。

  1. ISA 指针指向哪里?
    答:
    instance 对象的 isa 指针指向对应的类对象。
    类对象 class 的 isa 指针指向对应的元类对象。
    元类对象 meta-class 的 isa 指针指向基类的元类对象。

  2. OC 的类信息存放在哪里?
    答:
    类的成员变量值存放在 instance 实例中。
    类的属性、成员变量、协议、对象方法存在对应的类对象中。
    类的类方法存在在元类对象中。