objc-类的加载

589 阅读6分钟

前言

应用程序从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,代码较多

image.png 大致流程:计算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文件中类的内存地址是一样的,如下图 image.png image.png

Realize non-lazy classes

关于class的加载分为两种情况,一种是懒加载类,一种是非懒加载类(实现了+ (void)load{}方法的类)

  • 非懒加载类在readImages的过程就会走realizeClassWithoutSwift方法
  • 懒加载类需要等到类第一次接受消息的时候,在lookUpImpOrForward时,才会走类的实现方法 image.png 1111.png

类的实现

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中的方法,出现的是未知的符号 image.png 接下来的终点是研究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中打断点调试,调用堆栈如下:

image.png 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为本类的方法 image.png
  • 加载接剩余的分类,for循环之后,array()->lists[0]为分类的方法 image.png 这样分类的方、属性、协议等都加在进来了

分类实现了+ (void)load{} ,主类没有实现+ (void)load,则不会走attachCategory的方法逻辑,那么分类是怎么加载进来的? 看以下调用堆栈 12312.png 分类的加载在read_images时,就已经加载进去了
分类没有实现+ (void)load{} ,主类实现+ (void)load,情况和上述一致,都是在read_images阶段加载进去了分类数据
分类没有实现+ (void)load{} ,主类也没有实现+ (void)load,分类的数据加载推迟到了第一次向类发送消息的时候

分类和主类中实现的+load方法个数越多,则启动时间就会越长,因为中间牵涉了一部分算法,在这里进行计算