目录
- 一、前言
- 二、继续探索_read_images方法
- 三、realizeClassWithoutSwift方法分析
- 四、methodizeClass方法分析
- 五、懒加载类与非懒加载类
- 六、分类探索
- 七、如何将分类中的方法添加到类中
- 总结
一、前言
上一篇文章iOS dyld和objc的关联分析(类的加载上)中我们分析了dyld
和objc
的关联关系,但是类中的方法、属性、协议
什么时候添加到类中的,rwe
什么时候产生的这些问题依然没有解决。那么这篇文章我们就继续往下探索。
二、继续探索_read_images方法
这里重点分析_read_images
方法中处理non-lazy classes
的代码
通过上篇文章的分析我们从macho
文件中读取到类的地址
和名字
,接下来处理类中的ro、rw、rwe
等数据。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
// fixme also disallow relocatable classes
// We can't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
...
}
重点方法:realizeClassWithoutSwift
三、realizeClassWithoutSwift方法分析
注意:realizeClassWithoutSwift
方法调用的前提是这个类是非懒加载类
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) return cls;
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
...
递归调用realizeClassWithoutSwift方法实现父类及元类
// cls 信息 -> 父类 -> 元类 : cls LGPerson
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
// Attach categories - 分类
methodizeClass(cls, previously);
return cls;
}
realizeClassWithoutSwift
方法注释如下:
- 在类
cls
上执行首次初始化, - 包括分配它的读写数据。
- 不执行任何快速端初始化。
- 返回类的真实类结构。
- 锁定:
runtimeLock
必须由调用者写锁
技巧:在realizeClassWithoutSwift
方法中加入如下代码并打上断点即可只研究我们自定义的类
clean memory
:加载后不会发生更改的内存
dirty memory
:加载进程运行时会发生更改的内存。类结构一经使用就会变成dirty memory ,因为运行时会向它写入新的数据(eg:方法缓存)。
ro
:read only
-只读的,保留类的原始数据
rw
:read write
-外界通过rw
查找方法,首先判断是否存在rwe
,有就从rwe
中寻找方法,没有就从ro
中寻找方法。
rwe
:2020新增内容,由于rw
中的数据和ro
存在重复造成了内存浪费,而且只有运行时动态修改类的信息才会造成两者数据存在差异。为了节省内存从rw
中分离出会变化的rwe
,创建rwe
时首先会copy一份ro
中的数据,然后在修改或新增数据。因此使用了Runtime
修改了的类才有rwe
数据。
关于
ro、rw、rwe
的官方介绍:WWDC 2020 :Advancements in the Objective-C runtime
四、methodizeClass方法分析
在realizeClassWithoutSwift
方法中设置了ro、rw
并在最后调用了methodizeClass
方法,由此我们猜想methodizeClass
方法中设置了rwe
数据。
/***********************************************************************
* methodizeClass
修复cls的方法列表、协议列表和属性列表。
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
// realizeClassWithoutSwift 尽管看到了 data() -> ro -> rw (rwe)
// ro - methodlist - 方法查找的时候 (二分查找) sel 排序
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
if (!kc_isMeta) {
printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
}
}
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name));
}
ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
方法排序的步骤:
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
...
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
...
}
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
...
// Sort by selector address.
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
}
...
所以类中的方法列表是通过方法地址
SEL
排序的
虽然这里看到了rwe
但是rwe
却是NULL
因此
methodizeClass
方法中还是没有设置rwe
五、懒加载类与非懒加载类
- 非懒加载类:实现了
+load
方法(会提前加载,load_images
方法中会调用所有+load方法
) - 懒加载类: 未实现
+load
方法
下面研究懒加载类在什么时候加载?
准备工作:
- 注释
+load
方法
//+ (void)load{
//
//}
2.添加断点
-
realizeClassWithoutSwift
方法处理
因为要加载类就必然会调用realizeClassWithoutSwift
方法,所以在该方法中也添加断点
运行代码
- 由于懒加载类先执行
main
中的代码再调用realizeClassWithoutSwift
方法加载类,所以程序先在main
函数中中断
继续调试
打印出堆栈信息
2.通过堆栈信息可知当调用LGPerson
的alloc
方法后触发消息发送
->消息转发
->类的加载
现在我们知道了
懒加载类
的加载时机在于类的第一次消息发送
- 懒加载类和非懒加载类方法调用情况:
实际调用堆栈如下:
调用顺序为:map_images
->_read_images
->readClass
->realizeClassWithoutSwift
->methodizeClass
修改:
六、分类探索
由于在realizeClassWithoutSwift
方法的最后调用了methodizeClass
方法:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
...
// Attach categories - 分类
methodizeClass(cls, previously);
return cls;
}
methodizeClass
方法上面注释了Attach categories
,所以我们先来分析分类的结构
准备如下代码:
通过clang
将main.m
生成cpp
文件:
命令行代码如下:
cd main.m
目录
clang -rewrite-objc main.m -o main2.cpp
通过生成的cpp
文件我们可以发现LGPerson分类
转换为了如下C++代码
其数据结构,也就是分类的本质为:_category_t
-
_category_t
结构体中包含了两个_method_list_t
一个是实例变量
的方法列表,一个是类
的方法列表 - 由于分类没有协议,所以
protocols
赋值为0
,(cls
赋值也为0
,会在运行时赋上正确的值) - 由于结构体中没有成员变量列表,所以分类也不能添加成员变量
instance_methods、class_methods、properties
对应的C++
代码如下:
可以发现
instance_methods
中并没有属性cate_name、cate_age
的set、get
方法。所以分类
中可以添加属性
但是不会生成对应的set、get
方法,只能通过Runtime
动态添加set、get
方法。-
_method_list_t
中的数据结构为method_t
现在我们明白了分类的底层结构,那么分类是如何添加到类里面去的呢?
七、如何将分类中的方法添加到类中
由于之前我们猜想methodizeClass
方法中设置了rwe
数据,那么现在继续探索methodizeClass
方法后面的代码,我们注意到有这样的代码:
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
断点调试进入methodizeClass
方法
通过调试我们可以发现:
-
method_list_t *list
中只有类本身的方法,还没添加分类中的方法 - 虽然
rwe
为NULL
但执行了prepareMethodLists
方法将类中的方法进行了排序
继续调试我们可以发现if
中的代码不会执行:
然而进入attachToClass
方法后里面也没执行if
中的attachCategories
方法
重点方法attachCategories
在attachCategories
中添加如下代码并加上断点:
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
if (!kc_isMeta) {
printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
}
}
放开methodizeClass
中的断点继续运行会发现会进入attachCategories
方法
调用堆栈如下:
由此可得出以下结论:
- 虽然实现类调用的
methodizeClass
->attachToClass
后没调用attachCategories
方法,但是load_images
->loadAllCategories
后会调用attachCategories
方法。 -
attachCategories
方法中进行了rwe
初始化。
继续运行:
这里可以看到分类在运行时会修改category_t
中name
:由LGPerson
改为LGA
,并为cls
赋值。
在attachCategories
方法中通过调用extAllocIfNeeded()
生成了rwe
,因此调用extAllocIfNeeded()
的地方就会生成了rwe
。全局搜索extAllocIfNeeded ()
即可发现attachCategories
、addMethod
、class_addProtocol
、_class_addProperty
等地方调用了extAllocIfNeeded()
。
如果类在有
分类
,动态添加方法
、协议
、属性
的情况下才会生成rwe
。
现在我们知道了什么添加分类,什么时候初始化rwe
,但是什么时候将分类添加到类中不知道,后续文章将继续探索。
总结
- 非懒加载类在
main
函数之前加载(所以+load
方法非必要不要实现) - 懒加载类的加载推迟到类的第一次消息发送
- 分类中可以添加
属性
,但不会自动生成set、get
方法