OC对象的本质及分类
oc是一门面向对象的语言,底层实现其实都是c\c++代码,所以oc的面向对象都是基于c\c++的数据结构实现的,oc的对象、类主要是基于c\c++中的结构体来实现的
一、OC对象的本质
Object-C的底层都是通过C/C++来实现的,所以OC中的对象也会转化成C/C++中的某一个数据结构
1、如何将oc代码转换为c\c++代码?
终端进入要转换的文件目录下,执行以下命令
OC对象本质 % clang -rewrite main.m -o main.cpp
不同平台支持的代码不一样 平台: Windows,mac,iOS; 架构: 模拟器(i386),32bit(armv7),64bit(arm64)
OC对象本质 % xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
创建实例对象obj
NSObject *obj = [[NSObject alloc] init];
将oc代码转化为c++代码,我们可以看到NSObject的底层结构是:
struct NSObject_IMPL {
Class isa;
};
2、实例对象占据的内存空间大小
NSObject最终会转化成一个结构体,内部只有一个指向对象的结构体指针。 所以NSObject对象实际只是使用了8个字节类存储指针,但是系统实际分配了16个内存空间
// 实例对象的成员所占用的大小8 (实际使用的)
NSLog(@"%zd",class_getInstanceSize([NSObject class]));
// 整个结构体占用的是16(系统实际分配的)
NSLog(@"%zd",malloc_size(( **__bridge** **const** **void** *)(obj)));
通过阅读源码我们得知,当创建的对象分配的内存空间小于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;
}
3、sizeof、class_getInstanceSize、malloc_size的区别
-
sizeof: 是一个运算符,获取的是类型的大小(int、size_t、结构体、指针变量等),程序编译时获取
-
class_getInstanceSize: 是一个函数,程序运行时才获取,创建的对象加所有实例变量实际占用的内存大小, 内存对齐一般是以【8】对齐
-
malloc_size: 在堆中开辟的大小,向系统申请的空间大小 在Mac、iOS中的malloc函数分配的内存大小总是【16】的倍数
4、objc_getClass和object_getClass的区别
- Class objc_getClass(const char *aClassName)
-
传入字符串类名
-
返回对应的类对象
- Class object_getClass(id obj)
1)传入的obj可能是instance对象,class对象,meta-class对象
2)返回值
-
如果是instance对象,返回class对象
-
如果是class对象,返回meta-class对象
-
如果是meta-class对象,返回NSObject(基类)的meta-class对象
- -(Class)class,+(Class)class
- 返回的就是类对象,不管调用多少次
5、实例验证
1、 一个Person类继承了NSObject并且有两个int属性,占用内存多少
struct Person_IMPL {
Class isa; //8
int _age; //4
int _height; //4
};
@interface Person : NSObject
{
@public
int _age;
int _height;
}
@end
Person *per = [[Person alloc] init];
per->_age = 18;
per->_height = 170;
// 18 170
NSLog(@"%d, %d", per->_age, per->_height);
// 16 16
NSLog(@"%zd %zd",class_getInstanceSize([Person class]),
malloc_size(( **__bridge** **const** **void** *)(per)));
2、Student类继承Person,Person继承NSObject,占用内存多少?
@interface Person : NSObject
{
@public
int _age;
int _height;
}
@end
@interface Student : Person
{
@public
int _score;
}
@end
转换后的结构体结构如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
// struct NSObject_IMPL NSObject_IVARS;
Class isa;
int _age;
int _height;
};
struct Student_IMPL {
// struct Person_IMPL Person_IVARS;
Class isa; //8
int _age; // 4
int _height; // 4
int _score;// 4
};
验证:
Student *stu = [[Student alloc] init];
stu->_age = 20;
stu->_height = 180;
stu->_score = 85;
// 20 180
NSLog(@"%d, %d, %d", stu->_age, stu->_height, stu->_score);
// 24 实际只需占用20,但是根据内存对齐原则,应该是8的最小倍数 所以是 24
NSLog(@"%zd",class_getInstanceSize([Student class]));
// 32 系统分配
NSLog(@"%zd",malloc_size(( **__bridge** **const** **void** *)(stu)));
分析:Stu对象实际需要20,根据class_getInstanceSize的内存对齐原则【8】的倍数所以是24;而malloc_size实际申请空间,根据系统分配原则【16】倍数,所以结果是32
二、OC对象的分类
Object-C中的对象,简称OC对象,主要可以分为3种:
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
1、instance对象
instance对象就是通过alloc出来的对象,每次调用alloc都会产生新的instance对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
object1、object2是NSObject的instance对象(实例对象)
它们是不同的两个对象,分别占据着两块不同的内存
instance对象在内存中存储的信息包括:
- isa指针
- 其他成员变量
2、class对象
类在内存中也是以对象形式存储的, 类型:Class类型; 创建:
//实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
//类对象
// class方法返回的一直都是class对象 不管调用多少次
Class classObj = [[object1 class] class];
Class objectClass1 = [object1 class];
Class objectClass2 = [object1 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object1);
Class objectClass5 = [NSObject class];
//两个实例对象的地址不同
NSLog(@"%p, %p",object1,object2);
//这五个class对象的地址是一样的 是同一个对象
NSLog(@"%p,%p,%p,%p,%p,%p",classObj,objectClass1,objectClass2,objectClass3,objectClass4,objectClass5);
每个类在内存中有且只有一个类对象,地址都一样 class对象在内存中存储的信息主要包括:
- isa指针
- superclass指针
- 类的属性信息(@property)
- 类的对象方法信息(-号开头的方法 instance method )
- 类的协议信息(protocol)
- 类的成员变量信息(指的是成员变量的描述信息,如哪些变量,是什么类型,并不是实例对象的具体变量值)
类对象的本质结构
3、meta-class对象(元类对象)
Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"%p",objectMetaClass);
objectMetaClass就是NSObject的元类对象,元类对象也是每个类在内存中有且只有一个,元类对象和类对象在结构上非常相似。 元类对象在内存中存储的主要信息有:
- isa指针
- superclass指针
- 类方法(+号开头的方法 class method)
通过object_getclass方法既能获得元类对象 也能获得类对象
通过查看源码我们可以得知object_getclass会判断传进来的参数是类对象还是实例对象 如果是实例对象则返回类对象 如果传进来的是类对象则返回元类对象
可以通过下面的函数来判断对象是不是元类对象
BOOL result = class_isMetaClass(classObj); //no
BOOL result2 = class_isMetaClass(objectMetaClass); //yes
也就是说通过alloc创建的是实例对象 通过object_getclass(类对象)创建的是元类对象 其他对象则是类对象 但是类对象和元类对象有且只有一个 三类对象中 都含有isa指针,那么这个isa指针指向什么?
三、isa和superclass
1、isa指针
1)三类对象中的isa指向关系
三类对象中 都含有isa指针 其中isa指针的关系如下
实例对象的isa指向类对象 类对象的isa指向元类对象 元类对象的isa指向基类的元类对象 eg:一个实例对象想要调用对象方法,但是对象方法存放在类对象中,那么就是通过isa指针找到类对象中的对象方法再进行调用;同理,当调用类方法的时候,类方法是存放在元类对象中,类对象会通过isa指针找到元类对象,读取类方法列表中的类方法进行调用。
注:meta-class的isa指针指向基类的meta-class
2)对象的isa指针地址
由上知悉,实例对象的isa指针指向的是class对象,那么正常isa的指针地址其实就是class对象的地址。但是 从64bit开始,isa需要进行一次位运算(&ISA_MASK),才能计算出真实地址。
查看oc源码 opensource.apple.com/tarballs/ob… 可以看到ISA_MASK的定义
define ISA_MASK 0x0000000ffffffff8ULL
2、superclass指针
在类对象和元类对象中 都有一个superclass指针,其作用类似,都是指向父类对象
1)类对象中的superclass指针:
比如现在有一个Person对象继承自NSObject 有一个Student继承自Person 当studen的实例对象调用对象方法的时候,
- 首先实例对象会根据自己的isa指针去类对象中找有没有对应的方法
- 没有的话类对象会根据自己的superclass指针去父类的类对象中去查找(也就是student的类对象根据superclass指针去Person的类对象中去查找有没有对应的对象方法)
- 再没有的话Person的类对象会根据自己的superclass指针去NSObject的类对象中去寻找 (寻找到基类在没有对应方法的话就会报方法找不到的错误)
2)元类对象中的superclass指针
也是指引类对象去父类对象中寻找对应的类方法:
按照上面的例子,Student这个类 想调用一个类方法,
- 首先是Student的类对象 根据isa指针去Student的元类对象中查找有没有对应的类方法
- 没有的话Student的元类对象会根据自己的superclass指针去父类的元类对象(也就是Person的元类对象)中查找有没有对应的类方法,
- 再没有的话Person的元类对象再根据自己的superclass指针去NSObject的元类对象中寻找 有的话进行调用
- 没有的话NSObject的元类对象会根据superclass指针去NSObject的类对象中去寻找是否有相同名称的对象方法(这个地方下面会具体讲到为什么基类的superclass指针会指向对应的类对象)
3)isa、superclass总结
-
instance的isa指向class
-
class的isa指向meta-class
-
meta-class的isa指向基类的meta-class
-
class的superclass指向父类的class(如果没有父类,superclass指针为nil)
-
meta-class的superclass指向父类的meta-class(基类的meta-class的superclass指向基类的class)
-
instance调用对象方法的轨迹(isa找到class,方法不存在,就通过superclass找父类)
-
class调用类方法的轨迹(isa找到meta-class,方法不存在,就通过superclass找父类)