这篇文件来探索类的底层c++
一个自定义类的类名是咱们决定的,因此咱们想进行类探索,就得看看咱们定义的类,在底层是如何被定义的。缓存
先准备一个自定义类DZPerson
,在类中定义一个成员变量(_nick),一个属性(nameStr)。实现了一个方法(-saySomethine)和一个类方法(+sayHello)markdown
@interface DZPerson : NSObject
{
NSString *_nick;
}
@property (copy, nonatomic) NSString *nameStr;
@end
@implementation DZPerson
- (void)saySomething {
NSLog(@"%s", __func__);
}
+ (void)sayHello {
NSLog(@"%s", __func__);
}
@end
复制代码
在main中进行初始化。函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
DZPerson *person = [DZPerson alloc];
}
return 0;
}
复制代码
使用终端,进入到main.m
文件所在的路径,使用clang
命令,编译main.m
文件ui
clang -rewrite-objc main.m -o main.cpp
复制代码
输出main.cpp,打开这个文件,在里面进行搜索DZPerson
,做为线索的出发点:atom
typedef struct objc_object DZPerson;
⏬⏬⏬
struct DZPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_nick;
};
⏬⏬⏬
struct NSObject_IMPL {
Class isa;
};
⏬⏬⏬
typedef struct objc_class *Class;
复制代码
struct objc_object
结构体的别名。struct DZPerson_IMPL
结构体,包含两个成员属性:
struct NSObject_IMPL
,经过名字中的字段,了解到这个应该是父类NSObject
_nick
NSObject_IMPL
结构体,里面有一个成员isa
,是Class
类型。Class
是struct objc_class *
结构体指针类型。经过以上逻辑,咱们的目标转移到struct objc_object
和struct objc_class
这两个结构体上了。spa
看看这两个结构体在源码中的定义,代码比较长,我只截获了须要咱们研究的部分:3d
struct objc_object {
private:
isa_t isa;
public:
//这里定义了一些函数,先省略
...
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
//省略后续代码
...
}
复制代码
objc_object
中只有一个成员:isa
objc_class
继承自objc_object
:
objc_object
中的isa(苹果的注释仍是挺人性的)经过以上的源码查看和相关的简要分析,咱们能够确认自定义类
在底层会被转化成objc_class
类型的结构体。实例对象在底层就是objc_object
类型。经过下面的两行源码,能够直接证实:指针
typedef struct objc_class *Class;
typedef struct objc_object *id;
复制代码
此时看文章的你可能会有个疑惑:objc_class
继承自objc_object
,==那么类也是对象?==调试
OC中类也是对象,如何证实呢?咱们知道,实例对象中的isa指向的是类,类中也有isa,咱们经过lldb来看看类的isa: 咱们以文章开头的代码为例:
DZPerson *person = [DZPerson alloc];
复制代码
lldb执行截图:
x/4gx perosn
:首地址中的第一个值是person对象的isa。DZPerosn
,地址是:0x0000000100002378
0x0000000100002378
的内存状况,也就是查看DZPerson
类的内存DZPerson
的isa
,并po一下,打印的结果页是DZPerson
,可是地址和前面打印的地址不一样。这个第二个DZPerosn
是元类
。NSObject
,可是它不是NSObject
类,而是NSObject
的元类,也就是根元类
。而根元类
的isa指向仍是根元类
简单来讲,==实例对象isa指向类,类isa指向元类,元类isa指向根元类,根元类的isa指向根元类。==经过一张图,能够更好的理解isa的走位指向: 图中虚线表明isa走位,实现表明superclass走位。
==所以:类是元类的实例对象,而元类是根元类的示例对象。因此说,类也是对象,万物接对象==。
接下来咱们用lldb来看看类中存储的属性、方法,前文说到这些信息都存储在class_data_bits_t bits
中,使用指针偏移的方式就能够获取到bits。 此处放一张objc_class的源码截图: 拿到class在内存中的首地址,分别加上属性isa、superclass和cache占用的内存大小,就能够获得bits的地址。
isa
和superclass
都是指针类型,因此分别占用8字节。
那么如今须要计算cache占用多少字节,看看cache_t
的源码:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
//省略静态成员变量
...
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
//省略后面的函数
...
}
复制代码
mask_t
类型。指针占用8个字节,mask_t
是uint32_t
的别名,也就是占中4个字节。_flags
和_occupied
分别占用2个字节,由于是uint16_t
类型。cache_t cache
成员在结构体中占用16字节==那么想要获得class_data_bits_t bits
的偏移量,就是isa+superclass+cache=8+8+16=32,转换成十六进制就是20==
条件知足,咱们开始打印类中的信息:
DZPerson
的首地址首地址的第一个元素就是类的isa,因此咱们直接以十六进制打印便可。
获取bits
,拿到首地址进行+20偏移,20是前面计算出来的(0x0000000100002398 = 0x0000000100002378 + 20)。而且进行强制类型转换
调用class_rw_t *data() const
函数(前文中,objc_class
源码中的函数),获得地址,打印地址中的值:
接下来用class_rw_t
中的methods()
函数,能够获取到方法列表:
list
函数获取列表的首地址,打印地址的值后,用get查看到类中的全部方法。经过下图能看到类中定义的saySomething
方法、属性nameStr的setter和getter方法,还有一个系统生成的.cxx_destruct
方法:
+ (void)sayHello
,在这里是看不到的。由于类方法存在元类中。经过上面的方式咱们就能够产看到类中方法,也能够证实,方法是存在类中。一样,经过class_rw_t
中的properties()
能够获取到属性、protocols()
获取协议。
成员变量存在ro
中,经过函数ro()
获取:
objc_class
,而且知道类中也存在isa指针。但愿本文能对你有所帮组,若是有些的很差的地方,还请指出,谢谢!