一 .对象
、类
与isa
之间的关系
通过OC对象本质与 isa的研究中,我们发现每个对象都有成员变量isa
, 今天我们以此为入口来一起研究一下对象
、类
与isa
之间的关系。
话不多少,上代码,先创建两个类 YJPerson(继承NSObject)
、YJTeacher(继承YJPerson)
,用来测试;如下:
// YJPerson
@interface YJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
// YJTeacher
@interface YJTeacher : YJPerson
@property (nonatomic, copy) NSString *subject;
@end
1.1 isa 走位
/// isa 链路
void testIsaChain (id obj)
{
// 对象所属类 - 对象的isa指向
Class cls = object_getClass(obj);
// 元类 - 类的isa指向
Class metaCls = object_getClass(cls);
// 根元类 - 元类的isa指向
Class rootCls = object_getClass(metaCls);
// 根根元类 - 根元类的isa指向
Class metaOfRootCls = object_getClass(rootCls);
NSLog(@"对象的 isa -> : %@", obj);
NSLog(@"类的的 isa -> : %@, %p", cls, cls);
NSLog(@"元类的 isa -> : %@, %p", metaCls, metaCls);
NSLog(@"根元类的 isa -> : %@, %p", rootCls, rootCls);
NSLog(@"根根元类的 isa -> : %@, %p\n", metaOfRootCls, metaOfRootCls);
}
in main(int argc, const char * argv[]) {
@autoreleasepool {
// isa 链路测试
testIsaChain(NSObject.new);
testIsaChain(YJPerson.new);
testIsaChain(YJTeacher.new);
NSLog(@"Hello, World!");
}
return 0;
}
打印结果:
通过打印结果,得出结论:
对象 -isa-> 类 -isa-> 元类 -isa-> 根元类 -isa-> 根元类自己
1.2 superclass 走位图
/// super class 链路
void testSuperClassChain (Class cls)
{
Class superCls = class_getSuperclass(cls);
NSLog(@"父类 : %@, %p", superCls, superCls);
if (!superCls) {
NSLog(@"\n");
return;
}
testSuperClassChain(superCls);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 类的 superclass 链路
Class objCls = NSObject.class;
NSLog(@"类 : %@, %p", objCls, objCls);
testSuperClassChain(objCls);
Class personsCls = YJPerson.class;
NSLog(@"类 : %@, %p", personsCls, personsCls);
testSuperClassChain(personsCls);
Class teacherCls = YJTeacher.class;
NSLog(@"类 : %@, %p", teacherCls, teacherCls);
testSuperClassChain(teacherCls);
// 原类的 superclass 链路
Class objMetaClass = object_getClass(objCls);
NSLog(@"%@ 的元类 : %@, %p", objCls, objMetaClass, objMetaClass);
testSuperClassChain(objMetaClass);
Class personMetaClass = object_getClass(personsCls);
NSLog(@"%@ 的元类 : %@, %p", personsCls, personMetaClass, personMetaClass);
testSuperClassChain(personMetaClass);
Class teacherMetaClass = object_getClass(teacherCls);
NSLog(@"%@ 的元类 : %@, %p", teacherCls, teacherMetaClass, teacherMetaClass);
testSuperClassChain(teacherMetaClass);
}
return 0;
}
输出结果:
通过分析, 我们可以发现superclass 链路
可以分为两条:
-
类的
superclass 链路:类
-supercls->父类
-supercls->...
-supercls->NSObject类
-supercls->nil
-
类的元类
superclass 链路:类的元类
-supercls->父类的元类
-supercls->...
-supercls->NSObject的元类
-supercls->NSObject类
-supercls->nil
最终指向流程图:
至此,对于对象
、类
跟isa
的链路关系, 以及 类
跟superclass
的继承链关系,已经有一个清醒的认知了。
但是,类
的内部成员结构是什么样的,我们仍然不知道。继续探索。。。
二. 类的结构分析
基于objc4(838版本) objc_class 源码
struct objc_class : objc_object {
// 省略 ...
// Class ISA; // isa 继承自 objc_object 8字节
Class superclass; // 8字节
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();
}
// 省略 ...
}
-
isa
成员:继承自objc_object
的isa
,占8
字节 -
superclass
成员:Class
类型,Class
是typedef struct objc_class *Class;
定义的,是一个指针
,占8
字节 -
cache
成员:简单从类型class_data_bits_t
目前无法得知,而class_data_bits_t
是一个结构体
类型,结构体
的内存大小
需要根据内部的成员
来确定 -
bits
成员:只有首地址
经过上面3个属性的内存大小总和的平移,才能获取到bits
2.1 计算 cache
的内存大小
cache_t
的定义:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
// 省略无关代码。。。
cache_t
分析:
cache_t
有两个成员:_bucketsAndMaybeMask
和 一个联合体
-
_bucketsAndMaybeMask
是uintptr_t
无符长整型占8字节
-
联合体
里面有两个成员变量结构体
和_originalPreoptCache
(联合体的内存大小由成员变量中的最大成员变量的类型决定)-
_originalPreoptCache
是个指针占8字节
-
结构体
中有_maybeMask
,_flags
,_occupied
。_maybeMask
是uint32_t
占4字节
_flags
是uint16_t
各占2字节
_occupied
是uint16_t
各占2字节
结构体
大小是4 + 2 + 2 = 8字节
-
-
cache_t
的内存大小是8 + 8 = 16 字节
2.2 获取 bits
我们已经知道了类结构中 isa(8字节)
、superclass(8字节)
、cache(16字节)
的大小,接下来只需要通过地址偏移8 + 8 + 16 = 32
,即可得到 bits
的地址
- 其中的
data()
获取数据,是由objc_class
提供的方法
- 从源码 或 lldb调试中都可以看出
bits.data()
中存储的信息,其类型是class_rw_t
,也是一个结构体类型。但我们还是没有看到属性列表、方法列表
等,需要继续往下探索
2.3 探索 属性列表,即 property_list
通过查看class_rw_t
定义的源码发现,结构体
中有提供
相应的方法
去获取 属性列表、方法列表
等,如下所示:
在获取bits
并打印bits
信息的基础上,通过class_rw_t
提供的方法,继续探索 bits
中的属性列表
,以下是lldb 探索的过程图示:
-
p $2.properties()
命令中的propertoes
方法是由class_rw_t
提供的,返回值类型是property_array_t
-
list
的类型是property_list_t,是一个指针
,所以通过p *$5
获取内存中的信息
,同时也证明bits
中存储了property_list
,即属性列表
2.4 探索 方法列表,即 methods_list
在前文中提到的 YJPerson
中添加两个方法:实例方法sayHello
,类方法 eat
// YJPerson.h
@interface YJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)sayHello;
+ (void)eat;
@end
// YJPerson.m
@implementation YJPerson
- (void)sayHello { }
+ (void)eat { }
@end
2.4.1 方法的获取
参照属性列表的获取,我们来获取方法列表,如下图:
这里和属性获取不太一样,属性列表 property_list_t
里存储的是 property_t
,而 property_t
是这样的:
struct property_t {
const char *name;
const char *attributes;
};
所以属性通过索引可以直接输出属性的信息。而
方法列表method_list_t
存储的是 method_t
,method_t
是这样的:
struct method_t {
// 省略。。。
// 方法描述结构体
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
// 省略。。。
// big()方法 返回方法描述结构体
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
// 省略。。。
}
所以需要在获取 method_t
之后再调用 big()
方法,才能正常输出
2.4.2 方法分析
通过打印 method_list_t
中的每个 method_t
,发现并没有类方法eat
;由此可知:method_list_t
存储的是类的对象方法。那么 类方法eat
存在哪儿呢?不捉急,后续篇章为您揭秘。。。
总结
类中有isa
、superclass
、chche
、bits
成员变量,在对bits
探究过程中发现bits
存储着大家熟悉的属性列表
、方法列表
、成员变量列表
、协议列表
等,打开了我们对类
的认知,后面会继续对类进行探究,敬请期待