OC对象和类的本质

263 阅读4分钟

一、Objective-C的本质

我们平常编写的OC代码,底层实现其实都是C\C++代码,OC对象的底层其实是一个结构体,结构体里面有isa指针以及其他的信息。

将OC代码转换成c\c++代码可以验证:

  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件 

  • 如果需要链接其他框架,使用-framework参数。比如-framework UIKit 

二、OC对象的分类

可以分为三类:实例对象(instance)、类对象(class)、元类对象(meta-class)

  1. 实例对象:
  • 就是alloc出来的,每次alloc产生新的实例对象

  • 在内存中包含了isa指针和其他成员变量的值

  • alloc后内存空间就固定了,内存的大小和成员变量是相关的

  2.类对象:每个类在内存中有且只有一个class对象

    类对象中存储的主要信息包括:

  • isa指针

  • superclass指针

  • 类的属性信息(@property),类的成员变量信息(ivar)

  • 类的对象方法信息(instance method),类的协议信息(protocol)

成员变量的值是存储在实例对象中,以为只有当我们创建实例对象的时候,才会为成员变量赋值。但是成员变量叫什么名字,什么类型,只有一份就可以了,因此存储在类对象中。

3.元类对象:meta-class,每个类在内存中有且只有一个meta-class对象,他和class对象的内存结构是一样的,但是用途不一样,主要信息包括;

  • isa指针

  • superclass指针

  • 类的的类方法信息(class method)

  • 类的属性信息,成员变量信息,对象方法信息,类的协议信息,但其中的值是空的。

三、isa指针的指向

  • 实例对象的isa指针指向它的类对象,类对象的isa指针指向它的元类对象,元类对象的isa指针指向基类(NSObject)的元类对象,基类(NSObject)的元类对象的isa指针指向它自己。

  • 类对象的superClass指针指向它的父类的类对象,基类(NSObject)类对象的superClass指针指向nil,元类对象的superClass指针它的父类的元类对象,基类(NSObject)元类对象的superClass指针指向它的类对象。

  • 实例对象调用方法的轨迹: 通过isa找到class,方法不存在,就通过superClass找到父类
  • 类方法的调用轨迹:通过isa找到meta-class,方法不存在,就通过superClass找到父类
  • 不管是类对象还是元类对象,类型都是Class,class和mete-class的底层都是objc_class结构体的指针,内存中就是结构体

Class objectClass = [NSObject class];//获取类对象        
Class objectMetaClass = object_getClass([NSObject class]);//获取元类对象

四、Class的本质

    不管是类对象还是元类对象,类型都是Class, class和meta-class的底层都是objc_class结构体指针,内存中就是结构体。通过objc源码中去查找objc_class结构体的内容:

        类对象在内存中会包含了:isa指针、superclass指针、cache_t 类型的 cache(方法缓存) 、以及 bits 。 其中,通过这个 bits 就能查找到具体的类的信息。可以通过按位与 &FAST_DATA_MASK 就可以获取到一张class_rw_t类型的可读可写表。 

       class_rw_t类型的可读可写的表就包含了:方法列表(method_array_t)、属性列表(property_array_t)、协议列表(protocol_array_t)、class_ro_t只读表。class_ro_t里面存储了成员变量信息列表(ivar_list_t)。

       方法缓存 cache_t 内部有一个散列表 bucket_t,bucket_t 的key是 SEL,value是IMP。已经调用过的方法,就会缓存到cache_t。key 按位与& mask(一般是数组的长度) 计算出数组的一个index,重复的index就直接加1,散列表底层数组长度不够会扩容,扩容会重新计算。objc_msgSend 消息发送,在method_list 中找到方法的时候,会先把这个方法缓存到 cache_t 中,然后再使用指针调用方法。

附:主要的LLDB指令

补充:

1.一个NSObject对象,malloc的时候是被分配了16个字节,因为CF 规定一个对象至少要16个字节,但是他只用了8个字节,存放isa指针

创建一个实例对象,至少需要多少内存? 要参考结构体的内存对齐规则

#import <objc/runtime.h> 

class_getInstanceSize([NSObject class]); 

创建一个实例对象,实际上分配了多少内存? iOS操作系统的内存对齐规则,都是16的整数倍

#import <malloc/malloc.h> 

malloc_size((__bridge const void *)obj); 

2.为什么方法和属性列表这些不放在实例对象的结构体里面?因为每一个实例对象alloc出来都是不一样的,但是方法列表,属性列表这些一个类只需要一份就行,所以这些信息是存在了类对象里面