一、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)
- 实例对象:
-
就是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出来都是不一样的,但是方法列表,属性列表这些一个类只需要一份就行,所以这些信息是存在了类对象里面