美文网首页
iOS底层初探

iOS底层初探

作者: PageWen | 来源:发表于2019-07-21 23:34 被阅读0次

    OC底层实现原理

    oc对象以及类的是底层实现

    首先,通过数据结构的特性可以猜测类的底层应该是结构体这种数据结构,因为类和实例的所包含的数据类型是多样的,所以不会是数组这种要求类型相同的数据类型,下面借助clang查看下oc类在底层的源码,涉及到的clang的命令行为clang -rewrite-objc fileName -o newFileName,如果只想查看在某平台如iphone,可以使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc fileName -o newFileName,这里为了方便理解,以一个空属性类PQStudnet为例:

    这是oc代码

    #import "PQStudetnt.h"
    
    @implementation PQStudetnt
    
    @end    
    

    clang重写后的c代码如下,直接分析PQStudent的实现,可以通过源码印证我们之前的猜想,类的底层结构确实是一个结构体.通过一层层的推倒,我们可以发现最终类最后是一个结构体的指针typedef struct objc_class *Class;

    #ifndef _REWRITER_typedef_PQStudent
    #define _REWRITER_typedef_PQStudent
    typedef struct objc_object PQStudent;
    typedef struct {} _objc_exc_PQStudent;
    #endif
    
    struct PQStudent_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
    };
    
    struct NSObject_IMPL {
        Class isa;
    };
    
    typedef struct objc_class *Class;
    

    并且我们知道在32位机器上,指针占用4个字节,64位机器上,指针占用8个字节,所以可以猜想,在没有其他成员属性的情况下,实例对象所占的内存位8个字节,在下面的讨论中,我们也会进一步的验证这个猜想是否正确。这里查看内存大小使用的是runtimeclass_getInstanceSizemallocmalloc_size方法,两个方法分别表示实例对象所占内存和系统分配的实际内存

        PQStudent *student = [[PQStudent alloc] init];
        NSLog(@"%zu",class_getInstanceSize([student class]));
        NSLog(@"%zu",malloc_size((__bridge const void *)(student)));
    

    打印结果为:

    2019-07-21 22:18:16.868854+0800 PQDeepDemo[8314:803169] 8
    2019-07-21 22:18:16.868929+0800 PQDeepDemo[8314:803169] 16
    

    通过打印结果可以看出,实例对象所占内存位8个字节,但是系统却给实例对象分配了16个字节,对于这个很多同学可能会有疑问,为什么两个结果会不一致呢?这个实现就需要通过分析源码理解了,源码下载地址,下面看下‘class_getInstanceSize’在源码中的实现:

        uint32_t alignedInstanceSize() {
            return word_align(unalignedInstanceSize());
        }
        uint32_t unalignedInstanceSize() {
            assert(isRealized());
            return data()->ro->instanceSize;
        }
        static inline uint32_t word_align(uint32_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
        }
    

    其中# define WORD_MASK 7UL,所以可以看出该方法是按8字节的内存对齐策略,所以在没有其他成员变量,只有一个指针64位机长的情况下返回8;那为什么malloc_size返回16呢?所以我们就的看下allocWithZone的实现了,查看源码我们会发现有这么一段代码

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

    很容易发现在内存分配的过程中最小分配的单位是16字节,并且oc内存分配的时候内存对齐是按照16个字节。

    oc对象划分:实例对象、类对象、元类对象

    分析了oc的底层实现后,我们发现类结构体都有一个isa指针,那这个指针都指向哪里呢?实例对象、类对象、元类对象之间又是什么关系呢?下面会一一解答这些问题:

    首先,我们要明确,oc中有三种对象,分别是实例对象、类对象和元类对象,可以结合下面图片进一步加深理解这三个对象之间的关系 1515550530997239.png

    这里有同学可能会打印出实例的isa和其对应的类对象地址,会发现这两个不相等,这是因为在objective2.0之后isa添加了一个偏移量,需要&这个偏移量,通过图我们还能发现所有的元类对象的isa都是指向基元类对象的,并且基元类对象的superclass又指向根类对象。通过分析源码,我们还知道类对象中存储了实例方法,元类对象负责存储类方法,我们也清楚在找不到方法的时候,系统是会报找不到方法实现的错误的。但其实有个特殊情况,就是在元类对象中实现的方法,在根类方法中实现了,是不会报错的,并且会直接调用该实例方法。

    小结

    对于一门语言的底层要想深入的话,要不断的读取其源码,相对一些完全开源的语言,Objective-C的开源并没有那么彻底,所以我们在探究其底层实现,可能会遇到一些问题,希望同学们能积极的通过查找源码,大胆猜想的方式探索oc底层实现。最后,文章中如果有什么错误或者疑问,请在评论区指出。并且之后会添加关于其他oc底层实现的文章,如block的实现、kvo底层实现等,敬请期待!

    相关文章

      网友评论

          本文标题:iOS底层初探

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