在 WWDC2020中有一个视频讲的是关于Objective—C 在运行时的一些优化,本篇文章是对视频中讲到的的部分知识点进行探索。如果感兴趣的可以去看一下这个视频,或者看我上一篇文章:关于WWDC2020-Objective-C运行时的改进。
对视频中讲到的几个点做验证:
-
class_ro_t
存储着Flags
,Size
,Name
,Methods
,Protocols
,Ivars
,Properties
。 -
class_rw_t
存储着Methods
,Protocols
,Properties
。 -
为什么
class_ro_t
已经存储了Methods
,Protocols
,Properties
,而class_rw_t
还要存储这些? WWDC 中的介绍是class_ro_t
是只读的,存储的信息在编译时期就已经确定了,不能再更改,并且必要的时候可以清除,需要用的时候重磁盘中加载就好了。class_rw_t
是可读可写的,它信息的存储是在运行的时候,需要用到的时候才会写入,并且一直存在于内存中。
其中,第2点我们在前面的篇章中已经验证过了,确实有方法列表,协议列表和属性列表,但协议列表没有去验证,看完下面的验证方法后,再去验证class_rw_t
的协议方法,也是一样的。
我们先声明几个类,分别为继承自 NSObject
的 SHPerson
,SHPerson
的分类 SHPerson(Home)
,和继承自 SHPerson
的 SHTeacher
。
#pragma mark: - SHPerson
@interface SHPerson : NSObject
{
NSString *_name;
NSString *_age;
NSObject *family;
}
@property (nonatomic, copy) NSString *nickname;
@end
@implementation SHPerson
- (void)play_basketball {}
+ (void)playFootball {}
@end
#pragma mark: - SHPerson 的分类
@interface SHPerson(Home)
- (void)write_homework;
+ (void)eat;
@end
@implementation SHPerson(Home)
- (void)write_homework {}
+ (void)eat {}
@end
#pragma mark: - SHTeacher
@interface SHTeacher : SHPerson
@property (nonatomic, strong) NSString *course;
@end
@implementation SHTeacher
- (void)attend_class {}
+ (void)change_homework {}
@end
一、class_ro_t 的内存结构
在 class_rw_t
中,有这么一这个方法:
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
通过 ro()
这个方法可以拿到 class_ro_t
的数据,我们来看一下。
class_ro_t
中有 Flags
,Size
,Name
,Methods
,Protocols
,Ivars
,Properties
的成员变量,但是 baseProtocols
为nil,我们向 SHPreson
中添加协议,再运行看看。
#pragma mark: - Protocol
@protocol SHPersonProtocol<NSObject>
- (void)run;
- (void)sleep;
+ (void)work;
@end
#pragma mark: - SHPerson
@interface SHPerson : NSObject<SHPersonProtocol>
{
NSString *_name;
NSString *_age;
NSObject *family;
}
@property (nonatomic, copy) NSString *nickname;
@end
@implementation SHPerson
- (void)play_basketball {}
+ (void)playFootball {}
/// SHPersonProtocol
- (void)run {}
- (void)sleep {}
+ (void)work {}
@end
我们来重新打印一下:
baseProtocols
有值了,我们再来看一下源码中 class_ro_t
的结构。
二、class_ro_t 的 ivars
我们先来看一下class_ro_t
的 ivars
中都有什么:
确实有 SHPerson
中的四个成员变量,那如果我们在分类中添加属性或者成员变量,class_ro_t
的 ivars
和 baseProperties
是否也会存储呢?
可以看到在分类中添加实例变量语法都不过,那我们只能添加属性了,但其实大概也能猜出来添加属性就算语法过了,class_ro_t
中也不会存储。先来验证一下吧。
在分类中添加属性还要求添加相对应的 getter 和 setter。
准备好了之后,我们来验证一下:
并没有 height 属性,我们的猜想正确!这也是分类中不能添加属性的原因,如果想要添加属性,并且能正常使用,需要用到关联对象方法。
三、class_ro_t 的 baseMethodList
我们来看一下 baseMethodList
的内存结构:
我们发现 baseMethodList
是一个 void *const
的类型。还记得前面学过的,方法列表的类型吗?method_list_t
,我们把 void *const
转成 method_list_t *
。然后打印出 method_list_t
中 get
返回的 method_t
的 big()
方法。
我们发现报错了,来看一下 big()
的实现:
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
再来看一下 baseMethodList
在源码中的注释:
大概的意思是: 如果它指向一个小列表,则这是有符号的,但是如果它指向一个大列表,则可能是无符号的。
那既然 big()
不行,通过 name()
打印试试。
这个时候会发现,方法名称打印出来了,并且baseMethodList
还打印出了包括分类和协议中的所有实例方法!
四、class_ro_t 的 baseProtocols
接下来,我们再来看一下 baseProtocols 中是否也有我们相对应的协议方法。
这么一看好像看不出什么,我们来看下protocol_list_t
的源码:
struct protocol_list_t {
// count is pointer-sized by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
size_t byteSize() const {
return sizeof(*this) + count*sizeof(list[0]);
}
protocol_list_t *duplicate() const {
return (protocol_list_t *)memdup(this, this->byteSize());
}
typedef protocol_ref_t* iterator;
typedef const protocol_ref_t* const_iterator;
const_iterator begin() const {
return list;
}
iterator begin() {
return list;
}
const_iterator end() const {
return list + count;
}
iterator end() {
return list + count;
}
};
源码中也看不出什么,但是这个 list
在源码中是 list[0]
,我们把 list[0]
打印出来试试:
打印出来是protocol_ref_t
,我们再去看源码中 protocol_ref_t
是什么结构:
我们发现,protocol_ref_t
后面有个注释,protocol_ref_t
是一个 protocol_t *
类型的,我们来看一下 protocol_t
在源码中的结构:
这不就是我们想要的吗,接下来通过 lldb 将 protocol_ref_t
强转成 protocol_t *
并打印:
完全有我们想要的东西,接下来就是一顿操作:
通过验证,baseProtocols
确实存着协议方法。
五、class_ro_t 的 baseProperties
接下来,我们再来看一下 baseProperties 中是否也有我们相对应的属性信息。
不仅有 nickname
这个属性,还存有分类中的 height
属性和系统的其它属性。
六、firstSubclass
关于为什么 firstSubclass
为 nil 的问题,通过一段 lldb 打印来看一下:
七、总结
class_rw_t
存储着类的实例方法,协议方法,属性相关的信息
class_ro_t
存储着 Flags
(一些其它的数据,比如引用计数相关的),类的大小,类的名称,类的实例方法列表,协议方法列表,成员变量以及属性相关的信息。