美文网首页iOS 底层原理iOS基础知识
oc的本质、底层结构、内存分析、isa指针和superclass

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

作者: 鄂北 | 来源:发表于2021-10-17 00:06 被阅读0次

    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 <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

        本文链接:https://www.haomeiwen.com/subject/hhflnltx.html