oc的本质、底层结构、内存分析、isa指针和superclass指针分析

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文件从编译器中移除,否则在编译的时候会报错。

1-1.png

oc转c\c++详细流程可以看这里iOS将oc的.m文件编译成C++的.cpp文件

2、oc的底层实现

  • oc的底层实现都是c\c++代码,oc的面向对象都是基于c\c++的数据结构实现的
  • oc的对象和类主要是基于c\c++的结构体实现的
  • 编译器会先将oc代码转化为c\c++代码,再将c\c++代码转化为汇编语言,然后再转化为机器语言


    1-2.png

下面我们创建一个NSObject对象,然后再转化成c++代码,看下在c++中是什么样的结构

NSObject * object = [[NSObject alloc] init];

在oc中NSObject的定义是这样的

@interface 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这张图

1-3.png

  • instanceisa指向class
    当调用对象方法时,因为对象方法是放在class对象中的,所以instance对象会通过自己的isa指针找到class,最后找到对象方法的实现进行调用。

  • classisa指向meta-class
    当调用类方法时,因为类方法是放在元对象中的,所以类对象会先通过本身的isa指针找到meta-class,最后找到类方法的实现进行调用。

  • meta-classisa指向基类的meta-calss
    这里需要注意的是meta-classisa指向基类的meta-calss,而不是父类的meta-calss
    例如有A、B、C、D四个类,A是基类,D继承C,C继承B,B继承A,那A、B、C、D四个类的meta-classisa分别指向谁呢?
    答案是都指向A的meta-class,因为meta-classisa指向基类的meta-class,A是B、C、D的基类,因为A本身就是基类,所以A的meta-calssisa指向自己的meta-class

  • 从64bit开始,isa需要进行一次位运算,才能计算出真实地址

    1-4.png

6、superclass指针

  • superclass指针指向父类,classsuperclass指向class的父类,meta-classsuperclass指向meta-class的父类
  • classsuperclass指向父类的class,如果没有父类,superclass制作为nil
  • meta-classsuperclass指向父类的meta-class,基类的meta-classsuperclass指向基类的class
  • instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类,从父类的方法列表中找
  • class调用类方法的轨迹:isameta-class,方法不存在,就通过superclass找父类,从父类的方法列表中找

从1-4图中可以看到,instance中没有superclass指针,classmeta-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是一张非常经典的图大家可以看下


1-5.png

你可能感兴趣的:(oc的本质、底层结构、内存分析、isa指针和superclass指针分析)