准备工作
首先定义两个类
- 继承自
NSObject
的GLPerson
@interface GLPerson : NSObject
{
NSString *happy;
}
@property (nonatomic, copy) NSString *cjl_name;
- (void)sayHello;
+ (void)sayBye;
@end
@implementation GLPerson
- (void)sayHello {
}
+ (void)sayBye {
}
@end
- 继承自
GLPerson
的GLTeacher
@interface GLTeacher : GLPerson
@end
@implementation GLTeacher
@end
- 创建对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
GLPerson *person = [GLPerson alloc];
GLTeacher *teacher = [GLTeacher alloc];
NSLog(@"\n%@\n%@",person,teacher);
}
return 0;
}
元类
- 进入lldb调试
我们会发现:po 0x001d800100002485 & 0x00007ffffffffff8ULL
和po 0x0000000100002458 & 0x00007ffffffffff8ULL
的结果都是GLPerson,但是 地址又不一样。那是什么原因呢。 - 第一个打印$2是person的isa指针地址 与上 0x00007ffffffffff8ULL的结果是GLPerson类
- 第二个打印的$6是GLPerson的isa指针地址是GLPerson,那这个类是类的类 ,我们称之为元类.
什么是元类
我们都知道 对象的isa 是指向类,类的其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类
元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
元类是类对象 的类,每个类都有一个独一无二的元类用来存储 类方法的相关信息
元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称。
直接上图
从图中可以得出
1.person的isa指向GLPerson类
2.GLPerson的isa指向GLPerson元类
3.GLPerson元类的isa指向NSObjct根元类
4.NSObjct根元类的isa指向自己(地址相同)
可以看出最后的isa指向根元类,那这里的NSObjct和我们开发中用到的NSObjct类是同一个类么,我们两种方法验证一下
通过lldb验证
代码验证
用三种不同的方法获取类
Class class1 = [NSObject class];
Class class2 = [NSObject alloc].class;
Class class3 = object_getClass([NSObject alloc]);
NSLog(@"\n%p\n%p\n%p", class1, class2, class3);
}
输出结果如下
从图中可以看出,NSObject类的元类也是NSObject根元类,NSObject根元类的元类也是NSObject根元类。所以可以得出一个结论:内存中只存在存在一份根元类NSObject,根元类的元类是根元类自己。
我们通过不同方式获取的NSObject类对象,他们的地址相同,所以NSObject在内存中只有一份。同理继承于NSObject的类对象在内存中只存在一份。
isa走位图&继承关系图
objc_class & objc_objectisa
走位我们理清楚了,又来了一个新的问题:为什么没有对象
和 类
都有isa属性呢?
不提到两个结构体类型:objc_class & objc_object
我们之前提及NSObject的底层编译是NSObject_IMPL结构体
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
在objc4源码中搜索objc_class的定义,源码中对其的定义有两个版本
- 旧版 位于 runtime.h中,已经被废除,代码如下
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE; // 表示被废除
- 新版在objc-runtime-new.h,这个是objc4-781最新优化的,我们后面的类的结构分析也是基于新版来分析的。
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 // 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);
}
//太长了 省略一部分
unsigned classArrayIndex() {
return bits.classArrayIndex();
}
};
从新版的定义中,可以看到 objc_class 结构体类型是继承自 objc_object的。objc_object是这么定义的:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
objc_class
- 可以看出objc_object结构体有一个isa属性,所以继承于objc_object的对象有isa。
- objc_class结构体继承于objc_object,所以继承于objc_class的类有isa
总结
所有的对象都继承于obje_object。所有的类都继承于objc_class。objc_继承于objc_class。所以说所有的对象,类,元类都继承于objc_object。objc_object有isa属性,所以对象,类,元类都有isa属性。(万物皆对象)
类信息的内容
准备工作:
定义一个LGPerson类,添加以下属性和方法
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
@property (nonatomic) int hobby;
- (void)sayHello;
+ (void)sayBye;
我们要怎么能知道类的类内容呢?那现在就要打开objc_class的源码。
可以看到有四个属性:
isa属性:继承自objc_object,占
8
字节。
superclass 属性:Class类型的指针,占
8
字节。
cache属性:cache_t类型我们
并不知道
它占多少个字节,需要我们进入内部看。
![struct cache_t]](https://upload-images.jianshu.io/upload_images/1705709-2d4f9eb891b8c468.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可以看到是一个结构体类型,那就是8
字节了。
其实不对,只有结构体指针是8字节,而这时并不是指针,结构体的大小要看内部成员的总和。所以不是8字节。
注意图中,static在全局区,所以我们这里不需要计算。
所以就剩下了以下属性:
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic _buckets;
explicit_atomic _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
第一种情况
_buckets:bucket 是一个struct类型的指针,所以占8
字节。
///类型转换
typedef uint32_t mask_t
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
_mask:是mask_t类型,并不是常用类型,但是可以看到是其实是int类型所以占用4
字节。
flags:uint16是short的转换。占2
字节。
_occupied:也占2
字节。
占用内存为:8+4+2+2 = 16字节。
第二种情况
_maskAndBuckets:uintptr_t>类型,占用8字节
占用内存为:8+4+2+2 = 16字节。
所以cache占用16字节内存
前三个属性共占用内存32字节
根据内存偏移,获取bits需要将累的首地址平移(前三位的内存和)32位就可以得到
bits:
通过lldb获取属性: property_list
通过lldb获取方法: methods_list
总结
- 实例对象是类的对象,类是元类的对象,元类是根源类的对象,根源类是根根元类(自己)的对象
- 成员变量存储存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量
- 属性,存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性
- 类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,例如GLPerson类的实例方法sayHello 就存储在 GLPerson类的bits属性中,类中的方法列表除了包括实例方法,还包括属性的set方法 和 get方法
- 类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表,例如GLPerson中的类方法sayBye 就存储在GLPerson类的元类(名称也是GLPerson)的bits属性中