Object-C的底层都是经过C/C++来实现的,因此OC中的对象也会转化成C/C++中的某一个数据结构,c++
咱们在终端里经过指令数据结构
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp
将oc代码转化为c++代码,咱们能够看到NSObject的底层结构是:iphone
struct NSObject_IMPL { Class isa; };
Class是一个指向对象的结构体指针函数
typedef struct objc_class *Class;
因此NSObject最终会转化成一个结构体,内部只有一个指向对象的结构体指针ui
因此NSObject对象只会使用8个字节的内存空间来存储指针(固然 实际上给它分配了16个内存空间)spa
NSLog(@"%zd",class_getInstanceSize([NSObject class])); //实例对象的成员所占用的大小8 (实际使用的) NSLog(@"%zd",malloc_size((__bridge const void *)(obj))); //整个结构体占用的是16(实际分配的)
同时,经过阅读源码咱们得知,当建立的对象分配的内存空间小于16个字节的时候 系统都会分配16个字节的空间 这属因而苹果规定。
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; }
若是有一个student类继承了object而且有俩个int属性,那么student所占用的内存是多少呢?.net
student实际占用内存为16字节,系统分配的内存也是16字节。3d
假设有个person继承NSObject,student继承person,那么person和student各占用多少内存呢?指针
最终经过打印咱们发现,person,实际占用16,系统分配16,student实际占用16,系统分配16.code
为何?person实际占用16??int 4个字节 isa 8个字节 应该是12个字节啊?这就涉及到了前面写到的结构体内存对齐了。
oc中的对象主要能够分为三类:
1.实例对象,就是经过类alloc出来的对象,每次调用alloc都会生成一个新的实例对象
object1 和 object2 就是两个实例对象
实例对象在内存中存储的信息包括:
一、isa指针 (其实isa也算是对象的成员变量 也就是说实例对象内部只包含本身的成员变量)
二、其余成员变量(这里是存储成员变量的具体值)
二、类对象(class)
objectClass1-5 都是NSObject的类对象 ,由于每一个类在内存中有且只有一个类对象 因此上面五个类对象实际上是同一个对象
类对象在内存中存储的信息主要有:
一、isa指针
二、superclass指针
三、成员变量(这里的成员变量只是描述性的 好比有哪些变量 是什么类型的 并非实例对象的具体变量值)
四、类的对象方法(-号开头的方法)
五、类的协议信息和属性信息
类对象的本质结构↓↓↓
三、元类对象(meta-class)
objectMetaClass就是NSObject的元类对象,元类对象也是每一个类在内存中有且只有一个,元类对象和类对象在结构上很是类似。
元类对象在内存中春初的主要信息有:
一、isa指针
二、superclass指针
三、类方法(+号开头的方法)
咱们看到经过object_getclass方法即能得到元类对象 也能得到类对象 经过查看源码咱们能够得知object_getclass会判断传进来的参数是类对象仍是实例对象 若是是实例对象则返回类对象 若是传进来的是类对象则返回元类对象
咱们也能够经过下面的函数来判断对象是否是元类对象
也就是说经过alloc建立的是实例对象 经过object_getclass(类对象)建立的是元类对象 其余对象则是类对象 可是类对象和元类对象有且只有一个
三类对象中 都含有isa指针,那么这个isa指针指向什么?
实例对象的isa指向类对象 类对象的isa指向元类对象 元类对象的isa指向基类的元类对象
正是经过isa指针 才让三种对象产生关联
好比说,一个实例对象想调用对象方法 可是对象方法存放在类对象中 那么就是经过isa找到对象方法再进行调用
同理 当调用类方法的时候 类方法是存放在元类对象中的 类对象经过isa指针找到元类对象 读取类方法列表中的类方法进行调用
superclass指针
在类对象和元类对象中都有一个superclass指针,其实这两种对象中的superclass指针做用相似,都是指向父类对象
类对象中的superclass指针:
好比如今有一个Person对象继承自NSObject 有一个Student继承自Person 当studen的实例对象调用对象方法的时候,首先实例对象会根据本身的isa指针去类对象中找有没有对应的方法 没有的话类对象会根据本身的superclass指针去父类的类对象中去查找(也就是student的类对象根据superclass指针去Person的类对象中去查找有没有对应的对象方法 再没有的话Person的类对象会根据本身的superclass指针去NSObject的类对象中去寻找 寻找到基类在没有对应方法的话就会报方法找不到的错误)
而元类对象中的superclass指针也是指引类对象去父类对象中寻找对应的类方法:
按照上面的例子,Student这个类 想调用一个类方法,首先是Student的类对象 根据isa指针去Student的元类对象中查找有没有对应的类方法 没有的话Student的元类对象会根据本身的superclass指针去父类的元类对象(也就是Person的元类对象)中查找有没有对应的类方法,在没有的话Person的元类对象再根据本身的superclass指针去NSObject的元类对象中寻找 有的话进行调用 没有的话NSObject的元类对象会根据superclass指针去NSObject的类对象中去寻找是否有相同名称的对象方法(这个地方下面会具体讲到为何基类的superclass指针会指向对应的类对象)
关于上面提到的为何基类的superclass指针为何在找不到方法的时候会指向基类的类对象 也就是为何没有找到对应的类方法的状况下却能够调用同名对象方法?
关于这一点咱们经过代码来验证:
首先咱们新建一个NSObject的分类,在.h文件中声明一个test的类方法,但在.m文件中并未实现这个类方法 而是实现了同名的对象方法()
#import "NSObject+Test.h" @implementation NSObject (Test) //+ (void)test //{ // NSLog(@"+[NSObject test] - %p", self); //} - (void)test { NSLog(@"-[NSObject test] - %p", self); } @end
咱们调用类方法发现,及时没有对应的类方法,程序也能够正常运行,而且成功调用了同名的对象方法:
假如咱们在m文件没有实现同名test的对象方法,那么程序会报错的:
+[NSObject test]: unrecognized selector sent to class 0x7fffaddd7140
关于在h文件中有类方法的声明,这个是没有影响的 由于没有这个声明的话程序根本跑不起来 咱们关注的点是基类的superclass指针为何在找不到方法的时候会指向基类的类对象寻找同名的对象方法
好比咱们在h文件中声明了test的对象方法 m文件没有实现test方法 一样会报unrecognized错 这就是由于基类的对象方法中找不到方法后直接返回空值 而不是像类方法同样从元类对象找不到再去到类对象找同名对象方法
关于基类的superclass指针为何在找不到方法的时候会指向基类的类对象,这是由于oc在调用方法的时候其实是转换为c/c++去底层实现的 可是c/c++的底层实现并无区分类方法仍是对象方法 也就是没有区分+-号
好比
[NSObject test];
其实是转换为了
objc_msgSend([NSObject class], @selector(test))
没有区分+-号 因此在基类元类对象没有找到对应的类方法后回去基类的类对象中查看是否有同名的对象方法 有的话就调用 再没有的话就报错了