isa走位
由上可以得到0x0000000100008360 VS 0x0000000100008338 = Person,我们是不是可以猜测内存中其实不止一个类?验证一下
经过上面验证,我们发现类的地址是0x0000000100008360,那么上面提到的 0x0000000100008338到底是什么?对,就是我们的元类,侧面也验证了对象的isa指针指向的是类,类的isa指针指向的是元类。那么元类的isa呢指向的是什么?
由此我们能得到一个isa走位图
对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
根类 isa -> 根元类 isa
继承链
// 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);
打印输出得到:
此时写一个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);
打印输出:
此时我们能得出元类也有一条继承链:
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);
打印输出:
得出:根类的父类为nil,根元类的父类为NSObject,最终得到了这么一条继承链:万物皆对象
两张图合并起来得到了一张苹果给的图
源码分析类的结构
在分析之前,我们先了解一下内存的偏移
// 数组指针
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);
}
打印输出:
我们通过偏移指针就能拿到对应的值,那么同理我们找到类的首地址之后平移一些大小,就能拿到类里面的值。在源码里面找到当前类的成员变量
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;
...
}
第一个是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的内容。
类的属性和方法获取
我们拿到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这些才是关键信息
我们这里取
properties()得到全部的属性,拿到属性列表list的指针之后取值得到了一个property_list_t通过下标可以取到所有的属性。
@interface Person : NSObject {
NSString *subject;
}
我们此时在Person里添加一个属性,按照上面的步骤验证下可以发现subject并不在property_list_t里面
同理,我们获取Person里面的方法
为什么属性可以输出,但是方法虽然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就可以拿到方法信息了
分别打印输出之后发现是属性的set和get方法,在Person类里面加入对象方法也能打印输出,但是添加的类方法就没有显示。说明了什么?实例方法存储在类中,类方法存储在元类中。这里我们下篇验证下。
补充
__has_feature: 判断传入值是不是同时支持Clang和当前语言的标准(ARC)