iOS的NSObject

506 阅读3分钟

NSObject的定义

NSObject 是OC的基础对象类型,是通过C/C++的结构体进行面向对象封装而成的,在runtime的源码里,是对 Class 类型的 isa 的一个封装。

继而看到,Class 其实是结构体类型 objc_class 的指针。

isa

isa 在arm64架构之前,就是一个普通的指针,存储着 类Class 或者 元类meta-Class 对象的内存地址;但是从arm64之后,对isa进行了优化,变成了一个 union 共用体结构,与 struct 结构相似,但是 union 中多个成员共用一块内存,占用内存是是成员中长度最长的类型需要的内存大小。

ISA_BITFIELD 中,

  • 1、nonpointer:0,代表普通的指针,存储着Class、Meta-Class对象的内存地址;1,代表优化过,使用位域存储更多的信息
  • 2、has_assoc:是否有设置过关联对象,如果没有,释放时会更快
  • 3、has_cxx_dtor:是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
  • 4、shiftcls:存储着Class、Meta-Class对象的内存地址信息
  • 5、magic:用于在调试时分辨对象是否未完成初始化
  • 6、weakly_referenced:是否有被弱引用指向过,如果没有,释放时会更快
  • 7、deallocating:对象是否正在释放
  • 8、extra_rc:里面存储的值是引用计数器
  • 9、has_sidetable_rc:引用计数器是否过大无法存储在isa中;如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

各个字段后面的数字,表示字段占用的位(bit)个数,总长度还是 64位(8字节),依旧是一个指针的大小。变成 union 结构之后,isa 指针存放的信息更多更节省内存了。

NSOBject 对象的内存大小

查看对象的内存大小,我们一般通过两个API

//runtime.h   实际使用的内存大小
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
//输出 8

//malloc.h    实际分配的内存大小
NSLog(@"%zd", malloc_size((__bridge void *)obj));
//输出 16

在源码里面追踪 class_getInstanceSize ,得到的说明是返回的参数类的成员变量大小,向上取整到指针大小的界限。

创建对象时候分配内存我们都是使用的 alloc 方法,在源码里,方法的调用顺序是这样的(allocWithZone 也类似,最终落点是一样的)

最后落于 _class_createInstanceFromZone 这个方法

这个方法里,最终是使用 malloc_zone_calloc 或者 calloc 分配内存,而大小都是使用的 size_t size = cls->instanceSize(extraBytes); 这样一个方法来获取的大小,这个就是核心,那看看核心代码

可以看到分配内存的时候,分配的大小就是 alignedInstanceSize + 额外传入的大小,而且当size小于16个字节时自动扩展到16个字节。

如此,得到两点信息

  • class_getInstanceSize 方法返回的是对象实际需要占用的内存大小,结构体内存对齐(最大数据类型为isa指针),大小为8字节(指针大小)的倍数。
  • malloc_size 方法返回的是系统给对象分配的内存大小,对齐策略不一样,大小为16字节的倍数。

OC对象的分类

OC 的对象,主要分为三种

  • instance 实例对象,由类alloc出来的对象,每次调用alloc都会产生一个新的实例对象。
  • class 类对象,每个类在内存中有且只有一个类对象。
  • meta-class 元类对象,每个类的内存中有且只有一个元类对象。

实例对象 存储

  • isa指针
  • 其他成员变量(具体值)

类对象 存储

  • isa指针
  • superClass指针
  • 成员变量(成员变量描述性信息,比如变量类型之类,不是具体值)
  • 类的对象方法(- 实例方法)
  • 类的协议信息(protocol)
  • 类的属性信息(@property)

元类对象 存储

  • isa指针
  • superclass指针
  • 类方法(+ 方法)

三种类型的关系图总结