-
兄弟们,按理说应该一波搞定的~ 结果来了个上、下篇。(原谅我的无知。毕竟还没有探索完。也不敢保证😿)
-
上篇博客写了dyld 和 objc的关联,我们已经了解:APP启动后,会调用
map_images
和load_images
,今天,我们就根据这两个方法继续探究~
哎。。探究的过程肯定索然无味。也不知道有没有兄弟会看我的博客
1. map_images
分析
-
- 找到源码实现
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
兄弟们,其实探究源码最好的办法和验证,是可以断点调试,观察他的走向和流程。。如果没有源码的兄弟们,可以看看我原先的博客,有配好的
最好也自己配置下,和朋友吃饭可以吹牛皮😆
-
- 找到源码之后发现就
2
行,那我们直接点进去看看~
- 找到源码之后发现就
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
//内容过长,就先不贴了
}
当我们看到这么长的代码的时候:其实不止是自己。谁都慌、没有半点看下去的欲望!!
-
- 那我们把他的判断都折起来,然后通过注释,了解一下大概。(传说中的:先看总述,在细致分析)
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
if (firstTime) { … }
if (PrintImages) { … }
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{ … }
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) { … }
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// Call image load funcs after everything is set up.
for (auto func : loadImageFuncs) { … }
}
这样就舒服很多,不会很慌~
-
- 我们分析一波这里面做的啥。为了兄弟们方便,我截个图解释下:
1.1. _read_images
分析
-
- 兄弟们~,我们点进去观察一波读取镜像的方法
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//非常厉害的发现。这群人都有毒,写这么长,不就是不想让我看吗~
//那我必须研究一波啊。。
}
-
- 老规矩
咱们先看看总述,没用的折叠起来
- 老规矩
我就不贴了,毕竟代码比较长。不过折叠完,就比较清晰了~。
本来想看下注释解释然后截波图说明的,不过苹果也很亲切的写了log。
-
-
_read_images
流程分析
-
- 条件控制进行一次的加载
-
- 修复预编译阶段的
@selector
的混乱问题
- 修复预编译阶段的
-
- 错误混乱的类处理
-
- 修复重映射一些没有被镜像文件加载进来的类
-
- 修复一些消息
-
- 当我们类里面有协议的时候 :
readProtocol
- 当我们类里面有协议的时候 :
-
- 修复没有被加载的协议
-
- 分类处理
-
- 类的加载处理
-
- 没有被处理的类 优化那些被侵犯的类
-
-
- 既然大致的框架垒出来了。那我们要分析类的加载。就断点调试一波。找到关于类的方法
-
- 断点调试情况分析
然后
我们发现。走完这一步。
cls
突然间关联上类名。这里面肯定做了啥不为人知的事情(😆,最喜欢探究不为人知的秘密了~)
1.2. readClass
分析
-
- 我们从上面得出的结论是。
readClass
把cls
和类名
关联起来的~。
- 我们从上面得出的结论是。
-
- 老规矩,大致扫上一波。
既然。大致框架看了一下~那我们调试验证下
-
- 打印
mangledName
,观察分析
- 打印
我们观察到:这个应该是要关联的名字,既然这样,那我们就看我们自己写的类(毕竟知根知底~)
-
- 增加条件判断---指定判断类
这样的话,相信大家就比较舒服了~那就go on
-
- 我们通过打断点,分析主要代码
-
- 我们通过断点调试知道,就是
addNamedClass
,给了cls
名字。我们近距离观察下~
- 我们通过断点调试知道,就是
1.2.1 addNamedClass
方法小小分析一波~
-
- 找到源码:
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
-
- 这段代码也不长
我们断点跟几步
- 这段代码也不长
-
- 跟进去发现。连行注释都没有~(看不下去了)
走完
NXMapInsert
方法之后,这个类名就关联起来了~(做了一些不为人知的操作。好吧~这个代码服了。兄弟们有兴趣的可以研究一波。)
2. 懒加载与非懒加载
上面分析了下给
cls
类名。我们继续_read_images
向下分析。
-
- 我不是太清楚,大家对于
懒加载
的理解程度如何(大家肯定是比我强。)
- 我不是太清楚,大家对于
-
- 名词解释~我先解释一波吧(要是缺点什么,大家可以指点下😆):
懒加载:推迟到 第一次消息发送的时候~
非懒加载:当map_images的时候,加载所有类数据的时候就加载
懒加载和非懒加载的区别标志:当前类是否实现load方法(大家都知道load在main函数之前,其实就是你进入main之前我就加载你了)
2.1. 非懒加载分析
-
- 我们也可以根据
_read_images
中的注释分析
- 我们也可以根据
我们调试的时候没有走到这里,说明我们没有实现
load
,要是我们实现load
的话是不是在这里就可以观察类的加载情况了?那我们搞上一波~
-
- 实现
load
- 实现
#import "XGPerson.h"
@implementation XGPerson
+(void)load{
}
@end
我就只加了load,什么都没有变。运行下~
-
- 他站出来了~。
我们通过断点调试,发现他走的是
realizeClassWithoutSwift(cls, nil);
。那么我们研究下这个方法~
2.1.1. realizeClassWithoutSwift
分析
-
- 根据字面意思来看,他要实现这个类了。那我们在
XGPerson
中增加几个方法,看下他到底是怎么加进去的~
- 根据字面意思来看,他要实现这个类了。那我们在
@interface XGPerson : NSObject
- (void)say3;
- (void)say1;
- (void)say4;
- (void)say2;
@end
@implementation XGPerson
+(void)load{
}
- (void)say3{
NSLog(@"-%s-",__func__);
}
- (void)say1{
NSLog(@"-%s-",__func__);
}
- (void)say4{
NSLog(@"-%s-",__func__);
}
- (void)say2{
NSLog(@"-%s-",__func__);
}
@end
-
- 调试准备
在调试之前,偷偷的加一个自己的方法判断~去除冗余
const char *mangledName = cls->mangledName();
const char *XGPersonName = "XGPerson";
if (strcmp(mangledName, XGPersonName) == 0) {
auto xg_ro = (const class_ro_t *)cls->data();
auto xg_isMeta = xg_ro->flags & RO_META;
if (!xg_isMeta) {
printf("%s: 这个是我们的 %s \n",__func__,XGPersonName);
}
}
大家也可以使用自己的类作为调试~
-
- 总述~
由于代码过长,我感觉贴在这里可能对大家理解造成不好的后果,我就挑重点解释下
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
还有这个,代码太长了,截不到,就在这补一下~
这个,分类都出来了,那我要研究下~。(这个方法是必走的,那会不会在里面加的方法之类的?)
-
- 点进去看一波
大致分析一波
大家可以跟进方法里面,发现就是这加的。
这个里面会有
sel
的排序啥的,这个大家自己跟进来看下。(这篇博客太长了感觉。我精简下~)
2.2. 懒加载分析
-
- 我们知道
非懒加载
怎么加的方法属性。那么懒加载
呢
- 我们知道
-
- 我们搞一波代码(把load去掉,然后调用一波)
-
- 我们可以通过
bt
,或者 看堆栈
- 我们可以通过
他最终也会走
realizeClassWithoutSwift
,不过他是通过方法查找的时候实现类。
-
- 关于
懒加载
和非懒加载
的实现,我画了个图。(不好的地方兄弟们担待下~)
- 关于
3. 分类
兄弟们。刚才咱们看到
分类
了。那我们研究下分类的结构
(我知道~好多兄弟都知道。但是我还是写一下,自己巩固下印象)
3.1. 分类的本质
-
- 写一波分类
@interface XGPerson (XGTest)
- (void)cateA_1;
- (void)cateA_2;
- (void)cateA_3;
@end
@implementation XGPerson (XGTest)
//+(void)load{
//
//}
- (void)cateA_2{
NSLog(@"%s",__func__);
}
- (void)cateA_1{
NSLog(@"%s",__func__);
}
- (void)cateA_3{
NSLog(@"%s",__func__);
}
@end
这个最好写到main.m中,毕竟我们分析的是main.m。
-
- 我们用
clang
看下结构~ 命令:
- 我们用
clang -rewrite-objc main.m -o main.cpp
-
- 打开main.cpp文件
我们观察到(本来想贴代码,感觉代码的颜色不好看,还是截图清晰点)
-
- 我们找到这个类型
那我们就可以根据这个类型结构,了解里面的内容了。
我们了解了分类的本质,就是一个 _category_t
类型的结构体
3.2. 分类的加载
-
- 在上面,我们已经通过
methodizeClass
中找到了,分类的加载attachToClass
- 在上面,我们已经通过
-
- 我们研究下
attachToClass
- 我们研究下
这个函数也没有啥代码,根据断点调试他又没有走。就很难受。
-
- 那会不会是不用就不加载?我加
load
方法试下,会不会走一波关键方法attachCategories
?
- 那会不会是不用就不加载?我加
我们可以观察到他调用了,是在
load_images
调用的~
-
- 我们寻找关键代码,把一系列判断的都折叠
图截得有点乱(兄弟们见谅·~)
类的加载上,目前先到这~,等我写完下篇,应该会画个流程图啥的。😆