OC对象主要可以分为三种
instance对象(实例对象)、class对象(类对象)、meta-class对象(元类对象)
1. instance对象(实例对象)
clang -rewrite-objc main.m -o main.cpp
// 这种方式没有指定架构例如arm64架构 其中cpp代表(c plus plus)生成 main.cppNSObjcet实际上是只有一个名为isa的指针的结构体,因此占用一个指针变量所占用的内存空间大小,如果64bit占用8个字节,如果32bit占用4个字节。
instance对象在内存中的存储信息有 isa指针 和 其他成员变量具体值
2. class对象(类对象)
NSObject *obj = [[NSObject alloc] init];
Class objClass1 = [obj class]; //类对象1
Class objClass2 = [NSObject class]; //类对象2
Class objClass3 = objc_getClass(obj); //类对象3
三个都是NSObjet的class类对象,它们是同一个对象,每个类在内存中有且只有1个class类对象
class对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的属性信息(@property),
类的成员变量信息(ivar)//这里是指类型和名称,成员变量的值存储在实例变量里 - 类的对象方法信息(instance method),类的协议信息(protocol)
3. meta-class对象(元类对象)
Class objMetaClass = object_getClass([NSObject class])
objMetaClass是NSObject的metaClass元类对象
每一个类在内存有且只有一个meta class对象
Class objMetaClass2 = object_getClass( objc_getClass(obj) )
Class objMetaClass3 = object_getClass( objc_getClass( [obj class] ) )
objMetaClass2/3 也都是metaClass元类对象
另外:Class objClass = [[NSObject class] class];
class不管调用多少次 返回的都是类对象
查看Class是否是meta_class
BOOL isMtC = class_isMetaClass( [NSObject class] );//NO
BOOL isMtC = class_isMetaClass( objc_getClass([NSObject class] ));//YES
1. Class objc_getClass(const char *aClassName) 传入一个字符串类名,返回对应的类对象
2. Class object_getClass(id obj)
传入的obj可能是instance对象也可能是meta-class对象.
// 如果传入的是instance对象,就返回class对象,
//如果传入的是class对象,就返回meta-class对象,
//如果传入的是meta-class对象,返回NSObject(基类)的meta-class对象
3. -(Class)class, +(Class)class 返回的就是类对象
meta-class元类对象 和 class类对象 的内存结构是一样的,但是用途不同。
meta-class元类对象 :
isa指针
superclass指针
类的类方法
//runtime中传入类对象此时得到的就是元类对象
Class objectMetaClass = object_getClass([NSObject class]);
// 而调用类对象的class方法时得到还是类对象,无论调用多少次都是类对象
Class cls = [[NSObject class] class];
Class objectClass3 = [NSObject class];
// 后面两个地址相同,说明多次调用class得到的还是类对象
NSLog(@"%p %p %p", objectMetaClass, objectClass3, cls);
// 判断该对象是否为元类对象 class_isMetaClass(objectMetaClass)
每个类在内存中有且只有一个meta-class对象。
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的类方法的信息(class method)
所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。
总结:
例:对象的isa指针指向哪里、方法查找路径
- 当 实例对象 调用 实例方法 的时候,实例方法信息是存储在class类对象中的,那么要想找到实例方法,就必须找到class类对象,那么此时isa的作用就来了。instance的isa指向class类对象,当调用对象方法时,通过instance的isa找到class类对象,最后找到对象方法的实现进行调用。
- 当class类对象调用类方法的时候,同上,类方法是存储在meta-class元类对象中的。那么要找到类方法,就需要找到meta-class元类对象,而class类对象的isa指针就指向元类对象。class的isa指向meta-class当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用。
- 当对象调用其父类对象方法的时候,又是怎么找到父类对象方法的呢?,此时就需要使用到class类对象superclass指针。
当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用,同样如果Person发现自己没有响应的对象方法,又会通过Person的superclass指针找到NSObject的class对象,去寻找响应的方法 - 当类对象调用父类的类方法时,就需要先通过isa指针找到meta-class,然后通过superclass去寻找响应的方法
当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用
最后又是这张isa指向图,经过上面的分析我们在来看这张图,就显得清晰明了很多。
class的 isa指向 meta-class
meta-class的 isa指向 基类的meta-class,
基类的 isa指向 基类自己
meta-class的 superclass指向 父类的meta-class,
基类的meta-class的 superclass指向 基类的class
2. class调用类方法的轨迹,isa找meta-class,方法不存在,就通过superclass找父类
例:面试经常问到的
一个NSObject对象占用多少内存?
答:一个指针变量所占用的大小(64bit占8个字节,32bit占4个字节)对象的isa指针指向哪里?
答:instance对象的isa指针指向class对象,
class对象的isa指针指向meta-class对象,
meta-class对象的isa指针指向基类的meta-class对象,
基类自己的isa指针也指向自己。OC的类信息存放在哪里?
答:成员变量的具体值存放在instance对象。
对象方法,协议,属性,成员变量信息存放在class对象。
类方法信息存放在meta-class对象。
例:一个NSObject对象占用多少内存?
一个NSObject对象占用多少内存?
思考: 一个OC对象在内存中是如何布局的。
NSObjcet的底层C语言 结构体
struct NSObject_IMPL {
Class isa;
};
我们发现这个结构体只有一个成员,isa指针,而指针在64位架构中占8个字节。也就是说一个NSObjec对象所占用的内存是8个字节。但是
NSObject *objc = [[NSObject alloc] init]; 上面objc系统真正分配内存的时候是分配了16字节!
@interface Student : NSObject{
int _age;
int _no;
NSString *name;
}@endStudent *stu = [Student new];
//无成员变量 两个int成员变量 两个int + 1NSString
//8字节 16 字节 24字节
NSLog(@"Student实例大小为:%zd", class_getInstanceSize([stu class]));
//分配16字节 16 字节 32字节
NSLog(@"实际分配的内存空间:%zd", malloc_size((__bridge void *)(stu)));
Student对象首先会分配16个字节,存储3个东西,
isa指针8个字节,4个字节的_no , 4个字节的_age
总结:
class_getInstanceSize():说白了就是计算结构体所占内存的大小;
malloc_size():计算对象所开辟的内存空间的大小;
1. 结构体内存对齐,结构体的内存最终大小必须是最大成员大小的倍数。操作系统分配内存对齐的概念(Bucket Size).
在iOS堆空间中分配内存的话都是 16 的倍数。最好用对操作系统访问CPU是最优最快。
(如果结构体成员变量只需要 24 个字节,而iOS中分配堆内存,操作系统会分配32个字节的内存 )
class_getInstanceSize() //返回某个类对象至少需要多少空间
malloc_size() // 返回的是实际分配的内存空间