ios-类的加载过程(_read_images方法/懒加载类/非懒加载类)

1,173 阅读3分钟

ios-类的加载过程(_objc_init实现原理)

前言:

上篇文章分析了dyld和objc的关联,分析了_objc_init方法中的各个初始化方法

_dyld_objc_notify_registerdyld链接之间的关系。分析到_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

还遗留一个方法未分析,下篇文章继续分析分类是如何加载到内存中的