类的加载

我们知道dyld是链接各种库,而它本身也是一个库,从它初始化开始到init,大体流程就是_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> doInitialization -->libSystem_initializer(libSystem.B.dylib) --> _os_object_init(libdispatch.dylib) --> _objc_init(libobjc.A.dylib)
具体实现我们后续在说,这是只是通过dyld引出_objc_init

首先我们查看一下_objc_init源码

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    //读取影响运行时的环境变量,如果需要,还可以打开环境变量帮助 export OBJC_HELP = 1
    environ_init();
    //关于线程key的绑定,例如线程数据的析构函数
    tls_init();
    //运行C++静态构造函数,在dyld调用我们的静态析构函数之前,libc会调用_objc_init(),因此我们必须自己做
    static_init();
    //runtime运行时环境初始化,里面主要是unattachedCategories、allocatedClasses -- 分类初始化
    runtime_init();
    //初始化libobjc的异常处理系统
    exception_init();
    //缓存条件初始化
    cache_init();
    //启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
    _imp_implementationWithBlock_init();

    /*
     _dyld_objc_notify_register -- dyld 注册的地方
     - 仅供objc运行时使用
     - 注册处理程序,以便在映射、取消映射 和初始化objc镜像文件时使用,dyld将使用包含objc_image_info的镜像文件数组,回调 mapped 函数
     
     map_images:dyld将image镜像文件加载进内存时,会触发该函数
     load_images:dyld初始化image会触发该函数
     unmap_image:dyld将image移除时会触发该函数
     */
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

通过上述代码我们发现,在其中大部分是进行环境runtime缓存等的初始化,但是_dyld_objc_notify_register才是对dyld进行注册,通过&进行赋值,

0615865e0ed04fb1abf3f1641c55795c.png

mapimages整体流程.png

当我们查看map_images得知它是在将image镜像文件加载到内存时进行触发,具体代买如下:

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}

_read_images不仅仅是读取了类的信息,通过源码我们可以发现它读取了方法协议属性

1: 条件控制进行一次的加载
2: 修复预编译阶段的 `@selector` 的混乱问题
3: 错误混乱的类处理 
4: 修复重映射一些没有被镜像文件加载进来的 类
5: 修复一些消息!
6: 当我们类里面有协议的时候 : readProtocol
7: 修复没有被加载的协议
8: 分类处理
9: 类的加载处理
10 : 没有被处理的类 优化那些被侵犯的类

这里我们只对类的加载进行分析

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

    if (!doneOnce) {    
   // namedClasses
   //这个表中不包含预先优化的类。
   // 4/3是NXMapTable的装载因子
    int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//创建表(哈希表key-value),目的是查找快
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

    ts.log("IMAGE TIMES: first time tasks");
    }
// 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) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            //实现当前的类,因为前面readClass读取到内存的仅仅只有地址+名称,类的data数据并没有加载出来
            //实现所有非懒加载的类(实例化类对象的一些信息,例如rw)          realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

        Class cls = (Class)classlist[i];//此时获取的cls只是一个地址
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); //读取类,经过这步后,cls获取的值才是一个名字
        //经过调试,并未执行if里面的流程
        //初始化所有懒加载的类需要的内存空间,但是懒加载类的数据现在是没有加载到的,连类都没有初始化


    // Realize newly-resolved future classes, in case CF manipulates them
    //实现没有被处理的类,优化被侵犯的类
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[I];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }

通过上述代码我们得知readClassrealizeClassWithoutSwift才是重点,readClass读取了相关类的信息,realizeClassWithoutSwift是对类进行实现

readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
//增加一个监测,当类名为我们当前研究的类的话在进行分析,避免过多系统类扰乱思路
const char *LGPersonName = "LGPerson";
    if (strcmp(mangledName, LGPersonName)==0) {
        printf("%s -诶唷不错!- %s \n",__func__,mangledName);
    }

if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
    }
return cls;

通过上述代码我们发现返回的类信息由原先的地址通过addNamedClass关联到了名字,这样更改直观的查看相关信息

realizeClassWithoutSwift

如果是懒加载类,只有当第一次调用时才会进行触发,想要直接触发的话,可以在类中实现+load方法,这样就可以调用realizeClassWithoutSwift方法

截屏2021-12-16 下午5.16.57.png

接下来我们查看realizeClassWithoutSwift方法实现

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
//虽然这里进行了ro与rw的赋值,但是实际运行时这里并没有走
   if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        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);
//这里通过递归的方式对父类和元类以及根类进行实现
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// Attach categories
    methodizeClass(cls, previously);
    }
static void methodizeClass(Class cls, Class previously)
{
//在这里进行方法列表的绑定,
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
       //在进行prepareMethodLists方法之前,方法列表是无序的
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 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);
        }
    }
    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
}

通过打印我们可得知,在进行prepareMethodLists方法之前,方法列表存储是无序

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[I];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }

方法通过地址进行了排序(SortBySELAddress)

 void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        if (it != map.end()) {//除非在加载两次的时候才会走此方法,
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

2liucheng .jpg

上面我们通过调试发现attachCategories正常情况下并不会走,但是里面打印的话却走了根据流程分析得知,load_categories_nolock是肯定的会走的,但是attachCategories调用的地方都没有,故而全局搜索发现在loadAllCategories也调用了,并且也会调试到,而loadAllCategories方法是在load_images时调用的
所以能够反推出流程:反推路径为:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
分类加载正常的流程的路径为:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories

2251862-ad0bf2567887e494-2.png
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
//在这里对rwe进行初始化赋值,如果只有本类的情况下,通过cls就可以获取data()数据对rw与ro赋值,而如果添加完分类之后,这时就需要对rwe进行初始化赋值,保证原先rw的稳定,不去对其进行影响
    constexpr uint32_t ATTACH_BUFSIZ = 64;
   auto rwe = cls->data()->extAllocIfNeeded();
    method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
//这里是对方法进行倒序插入,更方便读取

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }
class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }

通过调试发现,只有当加载分类时,才会调用extAlloc,进而对rwe进行赋值,这样保证不会对本类rw进行污染

类与分类的加载时机

类和分类搭配使用,其数据的加载时机总结如下:

  • 【情况1】非懒加载类 +非懒加载分类,其数据的加载在load_images方法中,首先对进行加载,然后把分类的信息贴到类中

  • 【情况2】非懒加载类 + 懒加载分类,其数据加载在read_image就加载数据,数据来自data()data()编译时期就已经完成,即data()中除了类的数据,还有分类的数据,与类绑定在一起

  • 【情况3】懒加载类 + 懒加载分类 ,其数据加载推迟到 第一次消息时,数据同样来自data()

  • 【情况4】懒加载类 + 非懒加载分类 ,只要分类实现了load,会迫使主类提前加载,即在_read_images中不会对类做实现操作,需要在load_images方法中触发类的数据加载,即rwe初始化,同时加载分类数据

具体调试流程没有贴出来,可以通过在.m文件实现屏蔽load方法进行打印尝试

2251862-059469e1cfaa6020.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容