OC底层原理探索之类的原理分析上

309 阅读3分钟

isa走位

image.png 由上可以得到0x0000000100008360 VS 0x0000000100008338 = Person,我们是不是可以猜测内存中其实不止一个类?验证一下 image.png 经过上面验证,我们发现类的地址是0x0000000100008360,那么上面提到的 0x0000000100008338到底是什么?对,就是我们的元类,侧面也验证了对象的isa指针指向的是类,类的isa指针指向的是元类。那么元类的isa呢指向的是什么? image.png 由此我们能得到一个isa走位图 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类 根类 isa -> 根元类 isa image.png

继承链

	// NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);

打印输出得到: image.png 此时写一个Person继承于NSObject,Teacher继承Person

 // Person元类
    Class pMetaClass = object_getClass(Person.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"%@ - %p",psuperClass,psuperClass);
    
    // Teacher -> Person -> NSObject
    // 元类也有一条继承链
    Class tMetaClass = object_getClass(Teacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"%@ - %p",tsuperClass,tsuperClass);

打印输出: image.png 此时我们能得出元类也有一条继承链: Teacher的元类的父类指向Person的元类, Person的元类父类指向NSObject的元类;即元类子类继承于元类的父类,最终指向NSObject 接着分析NSObject,一般NSObject都会有特殊情况

	 // NSObject 根类特殊情况
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"%@ - %p",nsuperClass,nsuperClass);
    // 根元类 -> NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);

打印输出: image.png 得出:根类的父类为nil,根元类的父类为NSObject,最终得到了这么一条继承链:万物皆对象 image.png

两张图合并起来得到了一张苹果给的图 isa流程图.png

源码分析类的结构

在分析之前,我们先了解一下内存的偏移

  		// 数组指针
        int c[4] = {1,2,3,4};
        int *d   = c;
        NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
        NSLog(@"%p - %p - %p",d,d+1,d+2);

        for (int i = 0; i<4; i++) {
            int value =  *(d+i);
            NSLog(@"%d",value);
        }

打印输出: image.png 我们通过偏移指针就能拿到对应的值,那么同理我们找到类的首地址之后平移一些大小,就能拿到类里面的值。在源码里面找到当前类的成员变量

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;  
    ...
}

image.png 第一个是isa,第二个是NSObject,第三个是cache_t,第四个是class_data_bits_t,我们这里重点研究bits,根据上面平移我们自然而然想到可以平移(8 + 8+ cache_t)可惜我们不知道cache_t的大小

cache_t大小计算

我们进入struct cache_t发现好多好多代码,但是要记住struct的大小只跟成员变量有关,去掉里面的方法(方法区)和static(全局区)之后,发现关键就在这几行

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;	 // 8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; 		// UInt32 4字节
#if __LP64__
            uint16_t                   _flags; 			// 2字节
#endif
            uint16_t                   _occupied; 		// 2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; 	// 8字节
    };
    ...
}

explicit_atomic是一个泛型,关键大小由uintptr_t大小决定,这个是8字节 union联合共用体,“有你没他”,有struct就没有explicit_atomic,这个也是8字节 综上分析,所以cache_t的大小是16字节,回到上面的从首地址偏移(8 + 8 + 16)= 32字节可以得到bits的内容。 image.png

类的属性和方法获取

我们拿到Person类的首地址之后平移32位,然后就得到了class_data_bits_t,根据bits.data()拿到class_rw_t

 class_rw_t *data() const {
        return bits.data();
    }

进入class_rw_t我们可以看到想要的method_array_t property_array_t这些才是关键信息 image.png 我们这里取properties()得到全部的属性,拿到属性列表list的指针之后取值得到了一个property_list_t通过下标可以取到所有的属性。

@interface Person : NSObject {
     NSString *subject;
}

我们此时在Person里添加一个属性,按照上面的步骤验证下可以发现subject并不在property_list_t里面 同理,我们获取Person里面的方法 image.png 为什么属性可以输出,但是方法虽然count=8但是输出全部为空呢?我们发现方法property_t有成员变量

struct property_t {
    const char *name;
    const char *attributes;
};

但是我们找到method_t发现,只有获取到big才有那么,那么big怎么获取呢,继续翻找到了

struct method_t {
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    ...
        
     big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
    ...
}

所以此时此刻,我们获取到method_t的big就可以拿到方法信息了 image.png 分别打印输出之后发现是属性的set和get方法,在Person类里面加入对象方法也能打印输出,但是添加的类方法就没有显示。说明了什么?实例方法存储在类中,类方法存储在元类中。这里我们下篇验证下。

补充

__has_feature: 判断传入值是不是同时支持Clang和当前语言的标准(ARC)