美文网首页iOS
探索iOS底层原理开篇——对象本质

探索iOS底层原理开篇——对象本质

作者: 经天纬地 | 来源:发表于2019-05-10 21:42 被阅读3次

    作为一个iOS搬砖工,越来越感觉每天的重复搬砖工作没能给自己带来多大的提升,在这种不满足的情况下,是时候探索一下iOS底层原理实现,这篇文章将做作为开篇,记录这段时间学习底层原理的过程。
    首先抛出三个面试题:

    • 一个NSObject对象占用多少内存?
    • 对象的isa指针指向哪里?
    • OC的类信息存放在哪里?

    一、一个NSObject对象占用多少内存?

    大家都知道OC语言是C语言的超集,是对C语言上增加面向对象特性,大大简化开发人员编写代码量和不用怎么管内存管理。我们平时写的OC代码,其底层实现其实是C、C++代码,所以可以这么说:Objective-C的面向对象都是基于C、C++的数据结构实现的。再抛出一个问题,是基于C、C++什么数据结构呢?

    论证:

    1.新建一个命令行项目:在main.m编写以下代码:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSObject *obj = [[NSObject alloc] init];
        }
        return 0;
    }
    

    打开终端,定位到main.m文件所在位置,执行命令:

    xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc main.m
    

    得到main.cpp,从后缀名可以看出这是C++文件,代码很长,我们只需要关注最后的代码:

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            
        }
        return 0;
    }
    

    从转化后的C++代码可以看出NSObject调用alloc、init方法都是调用runtime的objc_msgSend方法,这里貌似看不太出什么东西,我们继续搜索NSObject,最后搜到一个这样的代码:

    struct NSObject_IMPL {
        Class isa;
    };
    

    从_IMPL(类似@implementation) 可以看出这里就是NSObject这个对象的具体实现,到这里已经明白,这就是一个C语言的结构体,里面包含一个Class类型的isa指针。

    回到问题1,OC对象占用多少内存:
    方法1:
    我们可以通过runtime的class_getInstanceSize方法获取实例对象占用内存:

    size.png
    方法2:
    通过malloc_size()方法
    size2.png
    可能有读者会有点奇怪,方法1和方法2获取的大小不一样。这是由于计算机原理的“内存对齐”原理,系统会给NSObject对象分配16个字节大小,实际占用是8个字节大小。

    方法三:Debug -> Debug Workfllow -> View Memory (Shift + Command + M)[图片上传中...(WX20190510-143756@2x.png-92f2e4-1557470289122-0)]


    image.png
    WX20190510-143756@2x.png

    方法四:通过LLDB命令,x + 对象或对象地址

    WX20190510-144045@2x.png

    扩展:

    对于一个自定义的类,占用多少内存?如一个Person类,包含四个成员属性:
    NSString *name,
    CGFloat weight,
    CGFloat height,
    NSInteger age,
    问:Person类对象占用多少内存空间?
    

    欢迎评论区讨论。

    二、对象的isa指针指向哪里?

    首先,OC对象分三种类型:
    1-instance对象(实例对象)
    2-class对象(类对象)
    3-meta-class对象(元类对象)

    1.instance对象(实例对象)

    就是通过alloc出来的类对象如

    NSObject *obj1 = [[NSObject alloc] init];
    SObject *obj2 = [[NSObject alloc] init];
    

    object1、object2是NSObject的instance对象(实例对象)
    它们是不同的两个对象,分别占据着两块不同的内存

    instance对象在内存中存储的信息包括:
    isa指针
    其他成员变量
    新建一个Person类:

    @interface Person : NSObject
    {
        int _age;
        int _name;
        int _no;
        NSString *name;
    }
    

    同样转成C++代码,发现Person的实现代码如下:

    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        int _age;
        int _no;
        NSString *name;
    };
    

    第一个成员是struct NSObject_IMPL NSObject_IVARS的结构体,搜索NSObject_IMPL发现代码:

    struct NSObject_IMPL {
        Class isa;
    };
    

    发现就是一个Class类型的isa,因此Person类的实现可以转化成:

    struct Person_IMPL {
        Class isa;
        int _age;
        int _no;
        NSString *name;
    };
    

    由此可以看出来,无论是NSObject最简单的类还是自定义的类,第一个结构体成员永远都是isa,所以isa的地址==对象地址,我们可以通过打印论证一下:


    image.png

    2-class对象(类对象)

    获取类对象有2种方式:

    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
            
    Class objectClass1 = [object1 class];
    Class objectClass2 = [object2 class];
    Class objectClass3 = object_getClass(object1);
    Class objectClass4 = object_getClass(object2);
    Class objectClass5 = [NSObject class];
    NSLog(@"实例对象%p %p",object1,object2);
    NSLog(@"类对象%p %p %p %p %p",
          objectClass1,
          objectClass2,
          objectClass3,
          objectClass4,
          objectClass5);
    
    打印结果如下: WX20190510-153059@2x.png

    objectClass1 ~ objectClass5都是NSObject的class对象(类对象)
    它们是同一个对象。每个类在内存中有且只有一个class对象
    class对象在内存中存储的信息主要包括
    1-.isa指针
    2-.superclass指针
    3-.类的属性信息(@property)
    4-.类的对象方法信息(instance method)
    5-.类的协议信息(protocol)
    6-.类的成员变量信息(ivar)


    WX20190510-163744@2x.png

    3-meta-class对象(元类对象)

    获取元类对象方法

    Class meteltClass1 = object_getClass([NSObject class]);
    

    objectMetaClass是NSObject的meta-class对象(元类对象)

    每个类在内存中有且只有一个meta-class对象

    meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
    1-.isa指针
    2-.superclass指针
    3-.类的类方法信息(class method)
    ......

    image.png
    回到问题本身,类的isa到底指向哪里?我们已经分析了一个类的对象分三种,而且这三种都有一个共同的成员isa,这个isa就是用来关联着三种类对象的,可以这么说:一个类的实例对象可以通过isa找到所在的类对象,类对象又可以通过isa找到它的元类对象,它们的关系如下:
    image.png
    instance的isa指向class
    当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用

    class的isa指向meta-class
    当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
    为了验证我的猜想,只能从官网下载objc源码:

    image.png

    另外补充一点,前面说到实例对象的isa指向类对象地址,按道理说isa存储的值就是类对象的地址,我们打印一下:


    image.png

    很明显两个值并不相同,原因是,从64bit开始,isa值会跟ISA_MASK相与,得到的新值才是类对象的地址,通过搜索源码可知
    arm64下ISA_MASK的值为0x0000000ffffffff8
    x86下的ISA_MASK值为0x00007ffffffffff8,打印如下:


    image.png
    两个值都为0x00007fff96bb9140,验证了我们的结论。所以上面的三种类对象isa之间的关系实际为:
    image.png

    三、OC的类信息存放在哪里?

    这个问题还要答吗?看上图。

    总结:

    一、一个NSObject对象占用多少内存?
    1-.系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
    2-.但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

    二、对象的isa指针指向哪里?
    1-. instance对象的isa指向class对象
    2-.class对象的isa指向meta-class对象
    3-.meta-class对象的isa指向基类的meta-class对象

    三、OC的类信息存放在哪里?
    1-.成员变量的具体值,存放在instance对象
    2-.对象方法、属性、成员变量、协议信息,存放在class对象中
    3-.类方法,存放在meta-class对象中

    相关文章

      网友评论

        本文标题:探索iOS底层原理开篇——对象本质

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