iOS底层学习 - 从编译到启动的奇幻旅程(三)

2,171 阅读22分钟

在上两章节,我们已经了解了一个App从编译到main函数的调用,发生了什么事情,并且我们知道了_objc_init在加载镜像文件时,会在dyld动态链接器中去注册,两者之前通过此来进行通讯。但是dyld加载相关镜像文件后,这些镜像文件是如何加载到内存当中的,是以什么方式存在于内存当中的,这就是本章探究的核心。

相关文章传送门:

iOS底层学习 - 从编译到启动的奇幻旅程(二)

iOS底层学习 - 类的前世今生(一)

iOS底层学习 - 初探类目、协议、扩展

我们知道dyld的主体流程就是链接动态库和镜像文件,那么objc的镜像文件本身是如何进行读取到内存中的,我们从源码来解读

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

通过源码,我们基本可以得出以下结论:

  • _objc_initlibSystem库调用
  • 在此方法里进行自举镜像加载到内存

下面我们逐步进行分析,objc相关类等,是如何加载的

准备工作

在镜像加载之前,objc进行了一系列的准备工作,我们来逐步分析,如下图:

environ_init

根据字面意思我们可以得出,这个方法是读取影响运行时的环境变量,可以使用 export OBJC_HELP=1 来打印环境变量,从而进行一些调试,可以再Xcode中进行设置,从而达到想要的效果打印。相关可以参考OBJC_HELP

  • OBJC_DISABLE_NONPOINTER_ISA 这个可以设置non-pointer的ISA,ISA的值不需要和mask进行与操作,直接指向
  • OBJC_PRINT_LOAD_METHODS这个可以打印类和分类的load方法,对我们进行启动优化很有帮助。

tls_init

这个函数是关于线程Key的绑定,比如线程数据的析构函数

static_init

根据注释可以得出,这个函数主要做了如下事情

  • 运行C++静态构造函数
  • 在dyld调用我们静态构造函数之前,libc会调用_objc_init(),所以必须在此前调用
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}

lock_init

是一个空实现,说明objc采用的是C++的加锁机制

exception_init

初始化 libobjc的异常处理系统,用来监控崩溃等,比如未实现的方法

_dyld_objc_notify_register

通过上一章,我们对这个方法已经有了了解,这是一个dyld的注册回调函数,从而让dyld可以链接加载镜像

  • 这个函数只在运行时提供给objc使用
  • 注册处理程序,以便在映射和取消映射和初始化objc镜像是调用
  • dyld将使用包含objc_image_info的镜像文件的数组,回调给mapped函数

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

map_images

这个方法是将镜像加载到内存时候,触发的主要方法,所以我们主要来探究这个方法是怎样将数据,类,分类,方法等以什么方式加载到内存中的。

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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的源码我们可以发现,如果hCount表示镜像文件的个数,则调用_read_images函数来进行加载镜像文件。所以加载内存肯定在此

_read_images解析

由于代码比较长,我们先做一个大体的概括,然后逐步进行研究,基本处理如下:

  • 第一次加载所有类到表中

    gdb_objc_realized_classes为所有类的表-包括实现和未实现的

    allocatedClasses包含使用objc_allocateClassPair分配的所有类(元类)的表

  • 对所有的类做重映射

  • 将所有的SEL注册到namedSelector表中

  • 修复旧的objc_msgSend_fixup调用导致一些消息没有处理

  • 将所有的Protocol都添加到protocol_map表中

  • 对所有的Protocol做重映射,获取到引用

  • 初始化所有非懒加载的类,进行rwro等操作

  • 遍历已经标记的懒加载的类,并做相应的初始化

  • 处理所有的Category,包括类和元类

  • 初始化所有未初始化的类

下面我们主要对类的加载来进行重点的分析

doneOnce

变量doneOnce表示这个操作只进行一次,因为是创建表的操作,所以只需要一次创建即可,主要的代码如下

    if (!doneOnce) {
        doneOnce = YES;
        ...
        initializeTaggedPointerObfuscator();
        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        //✅实例化存储类的哈希表,并且根据当前类数量做动态扩容
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        //✅创建一张包含所有的类和元类的表
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        //✅创建只包含已经初始化的类的表
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
        
        ts.log("IMAGE TIMES: first time tasks");
    }

类的重映射

类的重映射相关代码如下。主要是从列表中遍历出类,并进行处理和添加到相对应的表中

// Discover classes. Fix up unresolved future classes. Mark bundle classes.

    for (EACH_HEADER) {
        // ✅从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();
        
        for (i = 0; i < count; i++) {
             // ✅数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及自己创建的类
             // ✅这时候的类只有相对应的地址,无其他信息
            Class cls = (Class)classlist[i];
            
            // ✅通过readClass函数获取处理后的新类,下面具体分析readClass
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            //✅ 初始化所有懒加载的类需要的内存空间 - 现在数据没有加载到的 - 连类都没有初始化的
            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.

                //✅ 将懒加载的类添加到数组中
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

下面看readClass是如何处理类的?

我们可以看到有如下图的代码,里面对clsrw等进行了处理,我们知道rw里存了类的方法等,所以是不是在这里处理的呢?

但是我们在方法里打断点,发现并没有执行,说明我们创建的类和系统方法的类都没有走这个方法,所以类的rw数据填充并不是在此

通过红框中的判断,我们可得,这个判断条件是处理专门针对未来的待处理的类的特殊操作

那么继续向下可以看到如下代码,可以看到主要是执行了addNamedClassaddClassTableEntry两个函数

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

查看addNamedClass相关代码,主要将类添加到底层总的哈希表中

/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        //✅ 将类添加到总表中
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    assert(!(cls->data()->flags & RO_META));

    // wrong: constructed classes are already realized when they get here
    // assert(!cls->isRealized());
}

查看addClassTableEntry相关代码,因为当前类已经有了地址,进行了初始化,所以也要添加到allocatedClasses哈希表中

/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void addClassTableEntry(Class cls, bool addMeta = true) {
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    assert(!NXHashMember(allocatedClasses, cls));

    if (!isKnownClass(cls))
        NXHashInsert(allocatedClasses, cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

至此,初始化类已经添加到两张表中了

SEL添加到表中

SEL相关代码的处理如下,主要也是一个表写入的操作,写入了namedSelector表中,和类并不是一张表

    //✅ 将所有SEL都注册到哈希表中,是另外一张哈希表
    // Fix up @selector references
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->isPreoptimized()) continue;
            
            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                //✅  注册SEL的操作
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    }

将所有的Protocol都添加到protocol_map表中

相关代码如下。

    // Discover protocols. Fix up protocol refs.
    //✅ 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        //✅ cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        //✅ 获取protocol哈希表
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        //✅ 从编译器中读取并初始化Protocol
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

初始化所有非懒加载的类,进行rw、ro等操作

一个类的加载分为懒加载类非懒加载类

两者之前的主要区分是 是否实现了load方法

  • 如果实现了load方法,则为非懒加载类

  • 如果未实现,则为懒加载类

非懒加载类加载流程

由于非懒加载类实现了load方法,且dyld在进行镜像链接加载的时候,会调用各类的load方法,所以必须在镜像加入内存的时候,就做好对相关类的加载

    // Realize non-lazy classes (for +load methods and static instances)
    //✅ 实现非懒加载的类,对于load方法和静态实例变量
    for (EACH_HEADER) {
        //✅ 获取到非懒加载类的列表
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            //✅  从镜像列表中映射出来
            Class cls = remapClass(classlist[i]);
            // printf("non-lazy Class:%s\n",cls->mangledName());
            if (!cls) continue;

            // hack for class __ARCLite__, which did not get this above
#if TARGET_OS_SIMULATOR
            if (cls->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->cache._mask  ||  cls->cache._occupied)) 
            {
                cls->cache._mask = 0;
                cls->cache._occupied = 0;
            }
            if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) 
            {
                cls->ISA()->cache._mask = 0;
                cls->ISA()->cache._occupied = 0;
            }
#endif
            //✅ 再次插入到表allocatedClasses表中
            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 not disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            //✅ 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
            realizeClassWithoutSwift(cls);
        }
    }

查看realizeClassWithoutSwift相关代码

ro表示readonly,是在编译时刻就已经赋值的,但是此时rw还并没有赋值,所以这一步,主要是初始化rw

    // fixme verify class is not in an un-dlopened part of the shared cache?

    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }

通过下面的代码我们可以发现,此时对类的superclassisa进行了处理,会根据类的继承链关系进行递归操作,知道类为nil,也就是NSObject的父类。从而完成对类的继承链路的完善

if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

cls->superclass = supercls;
cls->initClassIsa(metacls);

但是isa找到根元类之后,根元类的isa是指向自己的,必然不给nil,如果不处理,就会形成无限递归,通过查看remapClass源码,我们可以知道,remapClass主要是对类在表中进行查找的操作,如果表中已有该类,则返回一个空值,如果没有,则返回当前类,这样保证了类只加载一次,并结束递归

/***********************************************************************
* remapClass
* Returns the live class pointer for cls, which may be pointing to 
* a class struct that has been reallocated.
* Returns nil if cls is ignored because of weak linking.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class remapClass(Class cls)
{
    runtimeLock.assertLocked();

    Class c2;

    if (!cls) return nil;

    NXMapTable *map = remappedClasses(NO);
    if (!map  ||  NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {
        return cls;
    } else {
        return c2;
    }
}

最后,我们发现函数最后调用了methodizeClass方法,根据明明,猜测是方法等的初始化

static Class realizeClassWithoutSwift(Class cls)
{
    ...
    // Attach categories
    methodizeClass(cls);
    return cls;
}

methodizeClass函数的实现中,我们发现了,刚刚初始化的rw的赋值,是从ro中取出相关数据,直接赋值给rw

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

那么attachLists是如何插入数据的呢

根据代码我们可以发现,主要通过把oldList向后偏移addedCount的位置,然后把新的addedLists整体插入到表的前面,从而实现分类的方法覆盖本类同名方法,所以分类的方法会比原方法先调用,并没有覆盖

    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;//10
            uint32_t newCount = oldCount + addedCount;//4
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;// 10+4
   
            memmove(array()->lists + addedCount, array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            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;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

一个非懒加载类的加载的主体流程如下

read_images内部先创建一个全局类的表gdb_objc_realized_classes和一个已经初始化的类的表allocatedClasses,之后对类进行初始化,并加载到表中,然后把SELProtocol等也映射到内存对应的表中中去,和类并不是一个表,并在对非懒加载类进行处理的时候,通过realizeClassWithoutSwiftro进行赋值,并且初始化rw,之后通过methodizeClassrw赋值,完成数据的加载

懒加载类加载流程

由于懒加载类并没有实现load方法,所以不需要在启动的时候就加载到内存中,那么懒加载类是什么时候加载到内存中的呢?

通过上面非懒加载类的探究,我们知道,非懒加载类会寻找父类,并进行加载,所以如果一个懒加载类是父类,则一定会在递归中被加载

另一种猜想就是,如果这个类调用了方法,进行了消息的发送,那么此时类是一定要有rw中的相关数据的,说明此时是肯定已经加载的。我们可以根据消息转发的源码探究

寻找函数lookUpImpOrForward,我们发现有一个!cls->isRealized()判断,是判断类有没有加载

if (!cls->isRealized()) {
    cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    // runtimeLock may have been dropped but is now locked again
}

通过打断点,我们可以发现,当类之前没有调用,第一次alloc的时候,会进入这个判断里面,继续深入,查看realizeClassMaybeSwiftMaybeRelock方法的实现

/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class. 
* Locking: 
*   runtimeLock must be held on entry
*   runtimeLock may be dropped during execution
*   ...AndUnlock function leaves runtimeLock unlocked on exit
*   ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        assert(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}

我们发现,这个方法主要是区分Swift,是OC时,主要运行了方法realizeClassWithoutSwift,这个方法,在上面非懒加载类流程中讲过,主要是对类的相关属性进行初始化

至此,一个懒加载类也已经加载完成了,总结一句话就是,一个懒加载类的加载,是在第一次被调用的时候才进行加载,并不是启动的时候就加载进内存

至此,一个类所需要属性的赋值加载都已经完成

处理所有的Category,包括类和元类

关于分类的一些基础的知识,可以参考下面的文章👇

传送门☞iOS底层学习 - 初探类目、协议、扩展

一个分类的加载和类的加载类似,也是需要区分load方法的调用

  • 懒加载的 - 编译时就确定
  • 非懒加载的 - 运行时确定

非懒加载分类

非懒加载的分类即实现了load方法的分类,我们可以和类加载一起来进行分析

1.懒加载类

通过上面的探索,我们知道懒加载类是在第一次使用的时候就会加载到内存中,那么由于分类是一个非懒加载的分类,他也需要加入到类信息的rorw中,所以这时候也会提前加载类,而不是等到发送消息的时候才初始化,具体代码如下

// ✅ 取出非懒加载分类列表
category_t **catlist = _getObjc2CategoryList(hi, &count);
// ✅ 判断是实例方法
if (cat->instanceMethods ||  cat->protocols  ||  cat->instanceProperties) 
    {
        ...
        // ✅ 添加到未绑定的分类列表中
        addUnattachedCategoryForClass(cat, cls, hi);
    }
...
// ✅ 判断是类方法
 if (cat->classMethods  ||  cat->protocols ||  (hasClassProperties && cat->_classProperties)) 
    {
       ...
       addUnattachedCategoryForClass(cat, cls, hi);
    }
// ✅ 由于是懒加载类,此时并没有加载,所以不走此判断
  if (cls->isRealized()) {
      remethodizeClass(cls);
      classExists = YES;    
  }

_read_image方法走完后,所有实现load方法的非懒加载类和分类都已经加入到表中,但是非懒加载分类此时并没有绑定到类中,所以在后续load_image函数,调用load方法时,会进行准备工作,此时会加载相关类,具体代码如下

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    ...
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        // ✅ 准备load方法的调用
        prepare_load_methods((const headerType *)mh);
    }
    ...
}
void prepare_load_methods(const headerType *mhdr)
{
    ...
    //✅  map_images 完毕了
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        // ✅ 加载对应的类和添加分类到load list
        realizeClassWithoutSwift(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

2.非懒加载类

非懒加载类和非懒加载分类的类比较简单,非懒加载类在_read_image中已经插入表,并进行了加载,所以在判断cls->isRealized()中已经进行了加载,所以直接执行判断方法remethodizeClass

我们可以发现remethodizeClass主要执行了attachCategories函数,将分类插入到类的rw

由于分类的方法是最后执行,所以方法插入时,如果有同名方法,会先找到分类的方法,但是类中本来的方法并没有被覆盖

懒加载分类

由于分类是懒加载的,所以_getObjc2CategoryList表中并没有这个方法,所以在_read_image方法里是没有办法检索出来的

懒加载分类是在编译的时候,直接就写入了类的ro中,不管类是非懒加载还是懒加载,当他进行加载的时候,直接蓉ro中取值,赋值给对应的rw

至此,相关镜像的加载已经完成

load_image

我们知道load_image方法是用来加载类和分类的load方法的,那么是如何调用的呢,我们还是根据源码来研究,主要发现以下两个步骤:

  • 准备load方法调用所需要的数据
  • 调用load方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    // ✅准备load方法调用所需要的数据
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // ✅调用load方法
    call_load_methods();
}

prepare_load_methods

这个方法是准备类和分类的load表的方法,主要是创建了非懒加载类非懒加载分类的列表,具体的实现如下

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // ✅遍历非懒加载类
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        // ✅加载相关类
        realizeClassWithoutSwift(cls);
        assert(cls->ISA()->isRealized());
        // ✅添加分类到非懒加载分类列表中
        add_category_to_loadable_list(cat);
    }
}

在添加非懒加载类到列表中时,会进行递归添加父类

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    // ✅递归添加父类
    schedule_class_load(cls->superclass);
    // ✅添加类到列表中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

在添加到列表中时,会先申请对应的空间,可以看到列表中存储的loadable_class的结构为16字节,所以申请现有空间2倍+16字节,并赋值对应的clsmethod

分类添加到列表中的逻辑和这个基本一样,只不过存储的列表和model的名字不同

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
         // ✅申请空间
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    // ✅model赋值
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

call_load_methods

call_load_methods方式是真正调用load方法的函数,主要操作如下

  • 调用时放入自动释放池
  • 循环遍历列表,调用对应的load方法
  • 调用完出栈自动释放池,保证调用一次
  • 根据调用顺序可知,类的load方法比分类的load方法先调用
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;
    
    // ✅压栈自动释放池
    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // ✅调用类的load方法
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // ✅循环调用分类load方法,仅一次
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    // ✅出栈自动释放池
    objc_autoreleasePoolPop(pool);

    loading = NO;
}

类具体调用load方法是在call_class_loads中,主要的操作为遍历非懒加载类列表,直接获取到IMP,强转为load_method_t,并直接调用函数实现,没有走消息发送流程

/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // ✅遍历列表,得到IMP并强转
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        // ✅直接调用函数
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

分类的调用和类的调用流程基本类似,只不过是从loadable_categories列表中获取数据,并且调用后释放,保证调用一次

static bool call_category_loads(void)
{
    ...
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }
    ...
    
    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }
    
}

至此,类和分类的load调用也已经完成了

initialize

通过上面我们已经知道了一个类的加载和load方法的调用,但是并没有找到initialize方法的调用,所以我们猜测是不是和懒加载类一样,会在消息发送的时候调用呢,果然我们找到了如下代码

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...
    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
    ...
}

// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    ···
    initializeNonMetaClass(nonmeta);
    ···
}

我们找到了initializeNonMetaClass方法,发现方法还是老套路,递归调用父类initialize,然后下方有一个真正的调用函数callInitialize

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
    ...
    // ✅递归调用父类
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    ...
    {
            // ✅调用Initialize方法
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
    ...
}

callInitialize这个方法已经非常熟悉了,就是普通的消息发送

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

至此Initialize方法也调用完成

总结

本章主要讲述了objc中类,方法,协议,分类等加载到内存中的步骤和load方法的调用流程

主要的流程如下:

  1. 准备相关环境变量,线程,锁,异常监听和dyld注册等前置条件
  2. 通过map_images中的_read_image来处理类,方法,协议,分类等
  3. 类的处理创建了一张总表和一张已经初始化的表来管理
  4. 非懒加载类在_read_image中处理,从ro中读取对应数据,赋值rw
  5. 懒加载类要在第一次被调用的时候才做类加载的相关处理
  6. 非懒加载分类在运行时确定,如果没有加载类,会做响应操作
  7. 懒加载分类在编译时确定,直接在ro中赋值给类
  8. load_image主要调用类和分类的load方法,并储存在不同的列表中
  9. 根据调用顺序,类的load比分类的先调用。其他方法,由于分类后插入rw中,所以分类比类的方法先调用,但是并没有与覆盖
  10. initialize在消息发送时调用