前言
应用程序从dyld加载之后,程序还不能运行,需要将image进行map映射可以跑起来,因为image文件的格式为mach-O格式,需要将其转化为我们所熟悉的类的结构体(objc_class)形式,这样就会将dyld链接动态库 -> map_image(加载类)-> load_images(load方法调用) -> main函数调用流程连接起来
dyld 和 libobjc 是怎么关联起来的
在dyld将动态库映射为image时,会递归调用image的初始化方法,对于libojbc来说入口就是_objc_init,_objc_init
会调用dyld动态库的_dyld_objc_notify_register(&map_images, load_images, unmap_image)
将map_images
函数地址和load_images
函数传入,等dyld的ImageLoader执行完doInitialization
之后,在内部通知调用 map_images 和 load_images,这样就能把类的加载工作传给libobjc,这个可以从 objc_init 这篇最后部分,xcode的调用堆栈可得知
map_images
打来源码libobjc,查询mag_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);
}
进入map_images_nolock
,代码较多
大致流程:计算class数量,调整各种表的大小,初始化sel方法表,重点则为:_read_images
,直接看_read_images方法
_read_images
其实就是将mach-O的
void _read_images(header_info **hList, uint32_t hCount, int
totalClasses, int
unoptimizedTotalClasses)
{
... //表示省略部分代码
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// 条件控制进行一次的加载
if (!doneOnce) { ... }
// 修复预编译阶段的`@selector`的混乱的问题
// 就是不同类中有相同的方法 但是相同的方法地址是不一样的
// Fix up @selector references
static size_t UnfixedSelectors;
{ ... }
ts.log("IMAGE TIMES: fix up selector references");
// 错误混乱的类处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover classes");
// 修复重映射一些没有被镜像文件加载进来的类
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) { ... }
ts.log("IMAGE TIMES: remap classes");
#if SUPPORT_FIXUP
// 修复一些消息
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
// 当类中有协议时:`readProtocol`
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover protocols");
// 修复没有被加载的协议
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up @protocol references");
// 分类的处理
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes.
if (didInitialAttachCategories) { ... }
ts.log("IMAGE TIMES: discover categories");
for (EACH_HEADER) {
//省略部分代码 ...
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
//读取类信息,将类名和类做关联,存到哈希表中,之后从表中去除类,做类的实现和加载
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 类信息发生混乱,类运行时可能发生移动,但是没有被删除,相当于常说的野指针
if (newCls != cls && newCls) {...}
}
// 类的加载处理
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code befor
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: realize non-lazy classes");
// 没有被处理的类,优化那些被侵犯的类
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) { ... }
ts.log("IMAGE TIMES: realize future classes");
...
#undef EACH_HEADER
}
主要流程:
- 条件控制运行一次加载
- 读取方法列表,修复预编译阶段的@selector混乱问题,就是不同类中,有相同的方法,但是相同的方法地址不一样,因为类的地址是不一样的
- 错误混乱的类处理
- 修复重映射一些没有北京向文件加载进来的类
- 修复一些消息
readProtocol
:当我们类里面有协议的时候- 修复没有被加载的协议
- 分类的处理
readClass
:将类名和类做关联,并把关联的类存储到哈希表中realizeClassWithoutSwift
: 初始化懒加载的类- 没有被处理的类,优化那些被侵犯的类
readClass
在readclass中打断点验证,加载的class地址,注意objc中的代码GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist")
,此处对应的是MACH-O文件的 Section64(_DATA, _objc_classlist)
地址和MACH-O文件中类的内存地址是一样的,如下图
Realize non-lazy classes
关于class的加载分为两种情况,一种是懒加载类,一种是非懒加载类(实现了+ (void)load{}
方法的类)
- 非懒加载类在readImages的过程就会走
realizeClassWithoutSwift
方法 - 懒加载类需要等到类第一次接受消息的时候,在
lookUpImpOrForward
时,才会走类的实现方法
类的实现
从dyld
到_objc_init
再到read_images
这整个流程的探究逐渐的串联起来了,脉络越来越清晰。后面就是非常重要内容
类的实现,熟悉后,就会帮我们解决以下问题
1、为什么要有rw,ro,rwe
2、+load方法的调用时机
3、+load方法调用顺序是怎么样的
4、分类中有和主类同样的方法,为什么调用分类的
realizeClassWithoutSwift分析
static Class realizeClassWithoutSwift(Class cls, Class previously) {
//省略部分代码...
//获取ro数据,这里可以打印,ro数据会标识实例变量大小,有成员变量,属性,但是方法未曾排序,没有进行过其他处理
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
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);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
//实现父类和元类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
//目前只研究自己写的类,系统的先忽略掉
if (strcmp(mangledName, "LGPerson") == 0){
if (!isMeta) {
printf("%s LGPerson....\n",__func__);
}
}
#if SUPPORT_NONPOINTER_ISA
//省略部分代码...
//元类的名字和本类的名字相同应该和它有关系,,
cls->setInstancesRequireRawIsa();
// SUPPORT_NONPOINTER_ISA
#endif
//设置isa走位图 和 父类子类继承链
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
//省略部分代码...
// Connect this class to its superclass's subclass lists
//形成查找的树状结构
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
//直到这里,查看数据,仍是未发现ro数据中methodList未曾处理,看来methods的处理应该是在下边的方法中,重点研究这个方法
methodizeClass(cls, previously);
return cls;
}
这个方法在忽略methodizeClass
前提下,实现了本类、父类、元类。使他们三个都拥有了干净的ro数据,建立了rw,但是ro中methods未做排序等处理,这时候打印ro中的方法,出现的是未知的符号
接下来的终点是研究methodizeClass
方法,看都干了哪些事,是否这个方法中有对method的name赋值的地方,直接打印出我们想要的方法名,而不是乱的符号
methodizeClass
static void methodizeClass(Class cls, Class previously) {
//省略部分代码...
// Install methods and properties that the class implements itself.
//获取methods
method_list_t *list = ro->baseMethods();
//准备方法,将method的name赋值,然后按地址大小进行排序,方便以后进行二分法进行查找
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
//attachlist:构成了方法列表的二维数组形式,将分类,类的方法都整合到一个数据结构,但是实际调试时,不会来这里,因为rwe还不存在,只有当load_images才会实例化rwe
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);
}
}
//表面意思试讲分类都附加在class,但实际上在这里也没有走attachcategory
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
}
类 & 分类都实现了
+ (void)load方法的同时
,read_image的流程就相当于走完了,ro 和 rw都有了值,但是分类还未加载进来,所以rwe还未初始化,接下来就该走load_image的流程了
attachCategory
流程
在attachCategory中打断点调试,调用堆栈如下:
attachCategory是在load_images中调用,里面都做了哪些事? attachCategory作用为,注册分类,然后重新build class的方法、协议列表等
if (slowpath(PrintReplacedMethods)) {
//省略部分代码...
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
//加载分类,将rwe进行初始化,并开辟响应空间
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {// ATTACH_BUFSIZ = 64
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
//取出分类的方法,进行排序,然后添加到list_array_tt结构中
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
//属性添加
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
//协议添加
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
方法主要是将分类的方法,协议及其他所需要的东西添加,等到需要时在进行调用 看下attachList内部方法,以及过程中的list_array_tt结构中数据的变化
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
} else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
} else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
- 加载第一个分类,attachList流程走到最下边的else逻辑,对应的数据如下 oldList为本类的方法
- 加载接剩余的分类,for循环之后,array()->lists[0]为分类的方法 这样分类的方、属性、协议等都加在进来了
分类实现了+ (void)load{} ,主类没有实现+ (void)load
,则不会走attachCategory的方法逻辑,那么分类是怎么加载进来的?
看以下调用堆栈
分类的加载在read_images时,就已经加载进去了
分类没有实现+ (void)load{} ,主类实现+ (void)load
,情况和上述一致,都是在read_images阶段加载进去了分类数据
分类没有实现+ (void)load{} ,主类也没有实现+ (void)load
,分类的数据加载推迟到了第一次向类发送消息的时候
分类和主类中实现的+load方法个数越多,则启动时间就会越长,因为中间牵涉了一部分算法,在这里进行计算