1、在开始前先说下怎么将oc代码转为c++代码
方法1
1、打开终端cd到目标的工程文件
2、终端输入:clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxx.m,其中xxx.m替换成自己需要转换的文件,然后敲回车
方法2
1、打开终端cd到目标的工程文件
2、xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp,将xxx改为自己需要转换的文件名才回车就可以了
如果需要链接其他框架,使用-framework参数。比如-framework UIKit
在终端上执行了上面两个方法中任何一个后,回到工程文件中就可以看到多了一个cpp文件,将cpp文件拖拽到工程中就可以在xcode里看到了
注意
在将cpp文件添加到工程中后最好将cpp文件从编译器中移除,否则在编译的时候会报错。
oc转c\c++详细流程可以看这里iOS将oc的.m文件编译成C++的.cpp文件
2、oc的底层实现
- oc的底层实现都是c\c++代码,oc的面向对象都是基于c\c++的数据结构实现的
- oc的对象和类主要是基于c\c++的结构体实现的
-
编译器会先将oc代码转化为c\c++代码,再将c\c++代码转化为汇编语言,然后再转化为机器语言
下面我们创建一个NSObject对象,然后再转化成c++代码,看下在c++中是什么样的结构
NSObject * object = [[NSObject alloc] init];
在oc中NSObject的定义是这样的
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
在c++中是一个结构体
struct NSObject_IMPL {
Class isa;
};
由此可以证明上面的结论oc的对象和类主要是基于c\c++的结构体实现的
class又是什么呢?clas是一个指针
typedef struct objc_class *Class;
如果创建一个Person类继承自NSObject其底层又是怎样实现的呢?
Person * person = [[Person alloc] init];
转化为c++后
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
NSObject_IMPL
就是NSObject的底层
struct NSObject_IMPL {
Class isa;
};
那么可以在Person_IMPL
中将NSObject_IMPL
看成是isa
指针,那么就等价于下面的写法
struct Person_IMPL {
Class isa;
};
如果Person带有成员变量呢?
@interface Person : NSObject
{
int _age;
NSString * _name;
}
其底层为
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
NSString *_name;
};
如果再创建一个Student类继承自Person并带有了height成员变量呢?
@interface Student : Person
{
int _height;
}
转化为c++后
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _height;
};
- 由上面的Person和Student转化为c++后可以看出,子类中包含了父类的结构体
- 每个对象都包含一个isa指针
3、oc对象内存详解
下面先用两个方法去打印NSObject对象的内存大小
NSObject * object = [[NSObject alloc] init];
NSLog(@"%zd",class_getInstanceSize([NSObject class]));
NSLog(@"%zd",malloc_size((__bridge const void *)object));
这两个方法分别打印的是8和16,为什么这两个方法打印出来的内存大小不一样呢。
class_getInstanceSize
是获取一个实例对象创建至少需要多少内存
malloc_size
是获取创建一个实例对象,实际上分配了多少内存
为什么会有一个最少内存和一个分配内存呢?因为oc中有一个内存对齐规则。
内存对齐:简单的理解就是最终的内存大小为成员中内存最大的整数倍,不足的要对齐。
在64位的环境下oc中对象存取是以8字节来计算的,对象开辟空间的内存是以16字节来对齐的。
想要详细了解iOS中内存对齐的可以看下面两位大神的文章,建议先看第一篇文章再看第二篇。
这篇通俗易懂的讲解了iOS中的内存对齐的应用
这篇很好的讲了内存对齐的定义
先看了第一篇才能很好的理解第二篇文章中内存对齐的定义
怎么计算出NSObject最少内存为8,分配内存为16呢?
因为NSObject在c++中实际为一个结构体
struct NSObject_IMPL {
Class isa;
};
在NSObject_IMPL
结构体中包含了一个isa指针,指针在64位的环境下为8个字节。结构体的内存大小所其成员所决定,又因为oc中对象存取是以8字节来对齐的
,NSObject_IMPL
结构体成员大小为8,刚好为8的整数倍不需要补齐,所以NSObject最少内存为8。因为oc对象开辟空间是以16字节对齐的
,NSObject的内存为8,不是16的整数倍,需要补齐是内存为16的整数倍,所以补齐后内存就为16了。
Person继承自NSObject,并有两个成员变量,那么Person的最少内存和实际分配内存分别是多少呢?
@interface Person : NSObject
{
int _age;
NSString * _name;
}
将Person转化为c++后
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
NSString *_name;
};
(本文章所说的内存都是在64位环境下的)NSObject_IMPL
里是一个isa指针,为8字节,_age
为4字节,_name
为8字节。8+4+8=20,又因为oc中对象存取是以8字节来对齐的
所以Person最少内存为24。因为oc对象开辟空间是以16字节对齐的
所以实际分配内存为32。
需要注意的是苹果为了节省内存空间对内存做了重排,所以在分配内存时,并不是按你的成员变量书写顺序去分配的
4、oc对象的分类
oc中的对象主要分为3类分别为:instance
对象(实例对象)、class
对象(类对象)、meta-class
对象(元类对象)。
1、instance
对象(实例对象)
通过alloc
出来的对象就是实例对象,每次alloc
都会生成一个实例对象。
NSObject * obj1 = [[NSObject alloc] init];
NSObject * obj2 = [[NSObject alloc] init];
NSLog(@"obj1:%p obj2:%p",obj1,obj2);
上面obj1和obj2就是两个不同实例对象,打印出内存地址分别是
obj1:0x10070a740 obj2:0x10070a750
内存地址不同就说明了是两个不同的对象,分别占据着两块不同的内存。
在上面对象本质的一部分,我们看到了实例对象在内存中存储了isa指针和成员变量。
2、class
对象(类对象)
每个类在内存中有且只有一个class
对象
NSObject * obj1 = [[NSObject alloc] init];
NSObject * obj2 = [[NSObject alloc] init];
Class objClass1 = [obj1 class];
Class objClass2 = [obj2 class];
NSLog(@"objClass1:%p objClass2:%p",objClass1,objClass2);
objClass1和objClass2都是类对象,打印出来的结果为
objClass1:0x7fff80670388 objClass2:0x7fff80670388
打印出来的地址是一样的,是同一块内存,所以是同一个class
对象。
class
对象在内存中存储的信息主要包括:isa
指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量(ivar)等。
3、meta-class
对象(元类对象)
每个类在内存中有且只有一个meta-class对象,meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括isa
指针、superclass
指针、类的类方法信息(class method)
获取元类对象需要用到runtime
Class metaClass = object_getClass([NSObject class]);
需要注意的是通过下面方法获取到的不是元类对象,而是类对象
Class objClass = [[NSObject class] class];
判断一个对象是否为元类对象,可以通过下面的方法(也是runtime中的方法)
BOOL result = class_isMetaClass([NSObject class]);
5、isa指针
由上面的知识点,我们已经知道在实例对象、类对象和元类对象中都有着一个isa
指针,isa
指针有什么用呢?我们先看下下面1-3这张图
instance
的isa
指向class
当调用对象方法时,因为对象方法是放在class
对象中的,所以instance
对象会通过自己的isa
指针找到class
,最后找到对象方法的实现进行调用。class
的isa
指向meta-class
当调用类方法时,因为类方法是放在元对象中的,所以类对象会先通过本身的isa
指针找到meta-class
,最后找到类方法的实现进行调用。meta-class
的isa
指向基类的meta-calss
这里需要注意的是meta-class
的isa
指向基类的meta-calss
,而不是父类的meta-calss
。
例如有A、B、C、D四个类,A是基类,D继承C,C继承B,B继承A,那A、B、C、D四个类的meta-class
的isa
分别指向谁呢?
答案是都指向A的meta-class
,因为meta-class
的isa
指向基类的meta-class
,A是B、C、D的基类,因为A本身就是基类,所以A的meta-calss
的isa
指向自己的meta-class
。-
从64bit开始,
isa
需要进行一次位运算,才能计算出真实地址
6、superclass指针
-
superclass
指针指向父类,class
的superclass
指向class
的父类,meta-class
的superclass
指向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
找父类,从父类的方法列表中找
从1-4图中可以看到,instance
中没有superclass
指针,class
和meta-class
中都有superclass
指针。
为什么instance
中没有superclass
制作呢,因为instance
中已经包含了父类的成员变量,所以根本不需要superclass
指针再去指向父类获取成员变量了。例如创建一个Student类继承自Person,那么Student的实例对象底层就是这样的。
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _height;
};
对这结构不是很明白的可以滑到文章上面再看下oc底层实现
这一块的知识点。
class对象的superclass指针
Person是Student的父类
当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用
meta-class对象的superclass指针
Person是Student的父类
当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用
1-5是一张非常经典的图大家可以看下