前言:
上篇文章分析了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
还遗留一个方法未分析,下篇文章继续分析分类
是如何加载到内存中的