前言:
上篇文章分析了dyld和objc的关联,分析了_objc_init方法中的各个初始化方法及
_dyld_objc_notify_register与dyld链接之间的关系。分析到_dyld_objc_notify_register方法中的&map_Images参数方法实现,及重点方法_read_images,因篇幅关系调用_read_images方法没有分析,这篇文章注重分析_read_images方法以及如何加载懒加载类和非懒加载类的。
概念:
-
懒加载类:没有实现
+(void)load方法,称做懒加载类 -
非懒加载类:实现了
+(void)load方法,称做非懒加载类 -
ro
ro表示readOnly,即只读,其在编译时就已经确定了内存,包含类名称、方法、协议和实例变量的信息,由于是只读的,所以属于Clean Memory,而Clean Memory是指加载后不会发生更改的内存 -
**rw **
rw表示readWrite,即可读可写,由于其动态性,可能会往类中添加属性、方法、添加协议,在最新的2020的WWDC的对内存优化的说明Advancements in the Objective-C runtime - WWDC 2020 - Videos - Apple Developer中,提到rw,其实在rw中只有10%的类真正的更改了它们的方法,所以有了rwe,即类的额外信息。对于那些确实需要额外信息的类,可以分配rwe扩展记录中的一个,并将其滑入类中供其使用。其中rw就属于dirty memory,而dirty memory是指在进程运行时会发生更改的内存,类结构一经使用就会变成ditry memory,因为运行时会向它写入新数据. -
rw可以理解为 rw的内存大小 = ro内存 + rwe额外内存信息
_read_images()
打开objc源码,搜索_read_images方法进入_read_images
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
bool launchTime = NO;
TimeLogger ts(PrintImageTimes);
runtimeLock.assertLocked();
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {...}
// 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
bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
// 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. rdar://problem/53119145
if (didInitialAttachCategories) {...}
ts.log("IMAGE TIMES: discover categories");
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// 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");
if (DebugNonFragileIvars) {...}
// Print preoptimization statistics
if (PrintPreopt) {...}
#undef EACH_HEADER
}
代码非常长,400多行,很懵逼,把能折叠的代码折叠起来。注释有Fix up字段的先不看,就剩上面的代码了,通俗易懂,下面已ts.log()方法为界一块一块的分析
doneOnce
修正sel
类的加载
类的重映射
一般不走这个方法
修正协议
非懒加载类
打印模块
以上就是_read_images方法中的解析。下面着重分析下类的读取中的readClass方法
readClass
进入readClass源码
上图为所有类都走的方法,无法确认是否是系统类和自定义了。在readClass类中创建LGPerson类名称
mangledName()
通过断点调试
addNameClass
addClassTableEntry
继续运行,返回className走到realizeClassWithoutSwift
realizeClassWithoutSwift
进入realizeClassWithoutSwift源码中
获取要研究的类
从Mach_O中读取数据赋值给ro,开辟rw内存空间,ro的数据copy给rw
查看ro数据
继续往下面走
查看rw,设置class_rw_t中并未完全成功,引出啦一个ro_or_rw_ext的字段类型,rw并未成功,那么往回走,走到set_ro的方法调用,进入源码:
set_ro的源码实现,其路径为:set_ro -- set_ro_or_rwe (找到 get_ro_or_rwe,是通过ro_or_rw_ext_t类型从ro_or_rw_ext中获取) -- ro_or_rw_ext_t中的ro通过源码可知ro的获取主要分两种情况:
-
如果
有运行时,从rw中读取 -
如果没有运行时,从
ro中读取
递归调用realizeClassWithoutSwift,确认继承连关系
设置isa关系链
双向绑定继承连关系,父类能找到子类,子类可以找到父类,最终到根类
添加分类
至此,类的加载过程分析完毕。以上是通过load方法,断点调试分析的非懒加载类的加载过程,那么懒加载类的调用过程如何。
懒加载类分析
打开源码,去掉load方法, 看下bt和堆栈
同样最终会走到realizeClassWithoutSwift方法中
总结:
_read_images的实现
类的加载过程
类的方法加载,分为懒加载类和非懒加载类的加载,他们两最终都会走到realizeClassWithoutSwift方法中,去实现代码到链接库到mach_o再到内存的过程。
- 懒加载类
lookUpImpOrForward
realizeClassMaybeSwiftMaybeRelock
realizeClassWithoutSwift
methodizeClass
调用堆栈: [LGPerson alloc] --> objc_alloc -->callAlloc --> _objc_msgSend_uncached -->lookUpImpOrForward -->initializeAndLeaveLocked-->initializeAndMaybeRelock-->realizeClassMaybeSwiftAndUnlock-->realizeClassMaybeSwiftMaybeRelock --> realizeClassWithoutSwift
- 非懒加载类
_getObjc2NonlazyClassList
readClass
realizeClassWithoutSwift
methodizeClass
调用堆栈:_dyld_start --> _objc_init --> _dyld_objc_notify_register --> dyld::registerObjcNotifiers --> dyld::notityBatchPartial --> map_images -->map_images_nolock --> _read_images --> realizeClassWithoutSwift
还遗留一个方法未分析,下篇文章继续分析分类是如何加载到内存中的