前言
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
是如何加载到类
里,让类
能够调用其内部的方法
的?我们还不清楚,期待下一次的分析。