iOS底层初探

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底层初探)