一、对象、ISA、类、元类、根元类间的关系
核心知识点:
- 类声明对象,给对象分配多少内存是依据类,对象的ISA指向类
- 对象在内存中第一个8字节储存的是ISA
- ISA中的shiftClass段就是类,即类为
对象的isa&mask
- 类也是一种对象,objc_class继承自objc_object,类的第一个8字节也是ISA,所以万物皆对象,万物皆有isa
- 类的ISA中的shiftClass为元类,即元类为
类的isa&mask
,类是由元类进行声明 - NSObject类的ISA指向NSObject类的元类,
NSObject类的元类
的ISA指向它自己 - 元类的ISA指向
元类的元类
,我们称NSObject类的元类
为根元类
- 元类的创建和声明是编译器完成,元类用来存储
类方法
的相关信息,元类
本身是没有名称的
,由于与类
相关联,所以使用了同类名一样的名称
- 对象之间不存在继承关系,只有类和元类之间存在继承关系
- 类与元类在系统中,只存在一份,即
类对象只有一份
- 无论类继承层次多深,类的元类的ISA指向的都是NSObject根元类,由对象开始查找ISA,第三次及以后绝对指向NSObject
1.1 准备验证代码
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface TESTPerson : NSObject
@property (nonatomic, copy) NSString *testname;
@end
@implementation TESTPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
TESTPerson *obj = [TESTPerson alloc];
TESTPerson *obj1 = [TESTPerson alloc];
NSLog(@"class:%@",[TESTPerson class]);
NSLog(@"class:%@",[obj class]);
NSLog(@"class:%@", object_getClass(obj));
NSLog(@"\n");
NSLog(@"class:%@",[[obj class] class]);
NSLog(@"class:%@",[[[obj class] class] class]);
NSLog(@"class:%@",[[[[obj class] class] class] class]);
NSLog(@"\n");
NSLog(@"class:%@", object_getClass(object_getClass(obj)));
NSLog(@"class:%@", object_getClass(object_getClass(object_getClass(obj))));
NSLog(@"class:%@", object_getClass(object_getClass(object_getClass(object_getClass(obj)))));
NSLog(@"\n");
}
return 0;
}
1.2 获取类的内存分布的三种方式:
- 根据对象的实例化方法:
[obj class]
- 根据类对象的类方法:
[TESTPerson class]
- 根据runtime中的
object_getClass(obj)
为什么嵌套调用时,结果不一致?
- class获取的永远是obj的类,因为传的是self
-
object_getClass
会将obj->getIsa()
当成下一次的obj,如果嵌套两层,相当于调用两次getIsa(),对应的代码obj->getIsa()->getIsa()
三种方式的源码分析:
- (Class)class {
return object_getClass(self);
}
//第一步,走类方法class
+ (Class)class {
return self;
}
//第二步,走objc_opt_class获取类信息
Class
objc_opt_class(id obj)
{
#if __OBJC2__
if (slowpath(!obj)) return nil;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return cls->isMetaClass() ? obj : cls;
}
#endif
return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));//走这里
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
1.3 Class的本质是objc_class
打开源码,在main方法里面输入Class *class
。然后通过Class点进去,可以发现都是typedef struct objc_class *Class;
,这说明Class(类)都是struct objc_class
创建而来的。
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
可以看到objc_class
是继承于objc_object
的,而objc_class
本身是没有isa
的,isa
继承自objc_object
,说明万物皆对象,对象是由类创建的,类由元类创建。
1.4 验证类信息只有一份
验证类信息只有一份.png二、类结构分析
2.1 重要:分析方法
分析的原理:基于类在内存中占用的地址是连续的,我们只需要从类的定义中,算出我们想查找的信息在哪个位置。具体做法就是通过struct objc_class : objc_object {}
,在这个定义类的结构体中,计算各成员变量所占的字节数,然后通过类的首地址,做相应字节的偏移,就能得到我们想要的信息。
再仔细看看objc_class的源码:
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; // class_rw_t * plus custom rr/alloc flags
//底下全是方法
......
}
C语言无法在结构体中定义函数,但是C++可以做到。C++中delete是删除地址空间,此处operator用作重载运算符。我们大概可以猜到前4句代码是析构函数功能,用作内存释放的函数。其他函数可以很确定绝对是函数的声明或者是内部函数实现。
所以我要弄清以下问题:
方法是否占用内存空间,会不会造成成员变量偏移?
通过以下代码予以验证
static声明的变量和方法不影响偏移位置.png同样的道理:计算struct cache_t所占用的内存大小:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
#if __LP64__
uint16_t _flags; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
计算出cache类的内存大小 = 12 + 2 + 2 = 16
字节。
我们所需要查看的信息在class_data_bits_t bits;
中,在bits前面有3个成员变量,isa,superclass,这两个都是Class类型,都占有8字节,cache占用16字节,那么bits的内存就是基于类的首地址偏移32字节。
2.2 class_data_bits_t具体操作
什么是class_data_bits_t?在源码中,全局搜索,共有三处。
//第一处
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
// The extra bits are optimized for the retain/release and alloc/dealloc paths.
//第二处
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
// 代码过多,自动省略
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 代码过多,自动省略
...
};
//第三处
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
在struct class_rw_t{...}
中,有我们熟悉的methods,properties,protocols。所以,我们应该先获取bits中class_rw_t *
类型的data,再访问data中的方法,属性和协议。
网友评论