前言
iOS 底层第15天的学习。在第14天的学习中,已经分析了 readClass,而 ro,rw 是在何时进行赋值我们还不清楚,接下来继续进行探索。
read_images 探索
- 在
read_images里继续寻找有关class代码
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//... 省略部分代码
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&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");
//... 省略部分代码
}
- 静态分析发现了在
non-lazy classes有关于class的处理, - 找到核心代码后加入
XKStudent进行普通类拦截 - 在分析前查看注解
(for +load methods and static instances)后发现要先在XKStudent类里实现load方法才会调用。 - 加入
load方法进行动态分析, 来到了realizeClassWithoutSwift
- 进入
realizeClassWithoutSwift,开始动态分析XKStudent类

- 打印输出👇

- 由输出的
methods_list count = 3我们得知在进行ro赋值时,已经把methods给加入到ro里,我们继续step

- 由⤴️得知:复制了一份
ro给rw,继续往下step


- 由⤴️ 两个代码可知:印证了
类的继承链图和isa走位图 - 继续
step进入methodizeClass
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();
// ...
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
// ...
}
- 进入
prepareMethodLists
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
// ...
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[I];
ASSERT(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
// ...
}
- 进入
fixupMethodList
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
printf(" name is %s - meth.name is %p \n",name,meth.name());
}
}
printf("----------------- Sort 后 ----------------- \n");
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
printf(" name is %s - meth.name is %p \n",name,meth.name());
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
- 我们 在
fixupMethodList加入了两段打印,一个是在sort前,一个在sort后,打印👇
- 得出的结论
methods的排列顺序是按照指针地址由小到大进行排序 - 最后我们梳理一下在
realizeClassWithoutSwift里做了哪些事情ro = cls->data(),ro的赋值rw = ro ,ro 复制一份给rw类的继承链, isa走位图初始化basemethods的排序
这时你是否会有个疑问,在上面可知只有实现
load方法才会调用read_images -> realizeClassWithoutSwift,当不实现load方法时是怎么加载的呢?
load 探索
- 把
load方法去掉,动态运行程序 - 我们发现没有调用
read_images,但还是会进入到realizeClassWithoutSwift这个方法里,觉得很奇怪bt一下
- 当把
load方法去掉,调用方法时候会发送消息进行lookUpImpOrForward进行慢速查找,最终也是会来到realizeClassWithoutSwift。这就是所谓的懒加载只有当方法调用的时候才会去做相应的ro,rw处理。数据加载推迟到第一次消息的时候。 - 而
非懒加载在map_images的时候,加载所有类数据。 - 流程图👇

what is category
- 新建一个
category,代码👇
@interface XKStudent (XK)
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
- (void) readBook1;
- (void) readBook2;
+ (void) readBook3;
@end
@implementation XKStudent (XK)
- (void) readBook1 {
NSLog(@"%s",__func__);
}
- (void) readBook2 {
NSLog(@"%s",__func__);
}
+ (void) readBook3 {
NSLog(@"%s",__func__);
}
@end
-
clang一下
clang -rewrite-objc main.m -o main.cpp
- 查看
.cpp文件代码
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_XKStudent_$_XK,
};
// 在 _category_t 生成了 CATEGORY_XKStudent_$_XK
- 全局搜索
_category_t
struct _category_t {
const char *name; // 别名 = XK
struct _class_t *cls; // 类的引用
const struct _method_list_t *instance_methods; // 存储实例方法
const struct _method_list_t *class_methods; // 存储类方法
const struct _protocol_list_t *protocols; // 存储协议
const struct _prop_list_t *properties; // 存储属性
};
- 全局搜索
_method_list_t查看一下方法的定义
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_XKStudent_$_XK __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"readBook1", "v16@0:8", (void *)_I_XKStudent_XK_readBook1},
{(struct objc_selector *)"readBook2", "v16@0:8", (void *)_I_XKStudent_XK_readBook2}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_XKStudent_$_XK __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"readBook3", "v16@0:8", (void *)_C_XKStudent_XK_readBook3}}
};
- 可知在
编译时 有实例方法和类方法,却没有属性get,set,所以我们可以通过runtime里关联对象进行处理 - 接下来通过底层源码来验证一下
category_t内部结构
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
- 根据
_classProperties注解验证了属性不是一直存在于disk,而是通过运行时去添加
还有一点疑问为什么
category的方法要将实例方法和类方法分开进行定义呢?
- 我想最主要的原因就是
category没有元类
总结
- 今天我们从
read_images来到了realizeClassWithoutSwift,在realizeClassWithoutSwift做了对ro,rw的赋值,以及类的继承链,isa走位图的处理; - 根据类
load方法的实现与否还得知了类的加载有懒加载和非懒懒加载,它们之间的流程是完全不同的; - 最后还简单的分析了
category的内部结构 - 但
category是如何加载到类里,让类能够调用其内部的方法的?我们还不清楚,期待下一次的分析。


