九:底层探索-类的加载

306 阅读18分钟

通过上一篇的内容- > 底层探索-应用程序的加载之dyld,我们从dyld的角度,探索了程序的加载过程。libObjcobjc_init方法中向 dyld 注册了回调_dyld_objc_notify_register,当 dyldApp 以及 App 所依赖的一系列 Mach-O 镜像加载到当前 App 被分配的内存空间之后,dyld会通知libObjc来完成具体的加载工作。今天的主角就是objc_init,我们将通过它来探究一下类的加载。

这里再啰嗦一句,大家看底层源码的时候一定要按主线流程去走,多看注释,多看注释,多看注释......

/***********************************************************************
* _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);
}

通过注释,大概也了解了它的作用,引导程序初始化,注册回调通知,由libSystem调用,其实中间还进行了libdispatch的初始化。

一:_objc_init初始化准备

1. environ_init()

作用:环境变量初始化

* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
void environ_init(void) 
{
    ......省略
    
    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}

方法顶部注释:1. 读取影响运行时的环境变量。2. 如果需要,还可以打印环境变量

我们可以越过判断条件将下面这段打印的代码提取出来,看看打印了什么

for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
        const option_t *opt = &Settings[i];
        if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
        if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
    }

可以看到的确打印了各种各样的环境变量,当然也可以在终端输入 export OBJC_HELP=1来查看更多的环境变量。我们同样可以在Xcode中来配置一些环境变量来进行调试。

2. tls_init()

作用:对线程key的绑定

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    _objc_pthread_key = TLS_DIRECT_KEY;
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

3. static_init()

作用:运行C++静态构造函数,需要注意的是只会运行系统级别的

/***********************************************************************
* 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]();
    }
}

4. lock_init()

注意:这是一个空函数,可能是为以后做预留

void lock_init(void)
{
}

5. exception_init()

作用:初始化异常处理,注册异常处理的回调,发生崩溃就会进入set_terminate函数中

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

6:_dyld_objc_notify_register

_dyld_objc_notify_register是我们今天探索的重点

// 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);

从注释部分我们可以知道:

  • 该方法只在objc运行期间中被使用;
  • 当objc镜像文件被映射、初始化、取消映射关系时注册处理程序;
  • dyld将会通过一个包含objc-image-info的镜像文件数组来回调mapped方法;
  • 该函数方法被调用的时候,会调用mapped函数对已经加载的镜像文件处理;
  • 当dyld调用initializers的时候回调用init方法;

_dyld_objc_notify_register中一共有三个参数:

  • map_images: dyldimage加载进内存时触发
  • load_images: dyld初始化image时触发
  • unmap_image: dyldimage移除时触发

其实这三个参数都是函数指针,与dyld中的_dyld_objc_notify_register方法中传入的参数一一对应。

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
	dyld::registerObjCNotifiers(mapped, init, unmapped);
}

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// record functions to call
	sNotifyObjCMapped	= mapped;
	sNotifyObjCInit		= init;
	sNotifyObjCUnmapped = unmapped;
    ......省略部分
}

二:map_images

在上面已经提到当dyldimage加载到内存的时候会触发该方法的调用。而在map_images中会调用map_images_nolock,而在map_images_nolock中,我们关注的重点必然是_read_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);
}

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

由于_read_images代码过长,以下代码摘出重要部分,并加以注释

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){
    // 1.第一次进来 - 开始创建表 
    // gdb_objc_realized_classes : 所有类的表 - 包括实现的和没有实现的
    // allocatedClasses: 包含用objc_allocateClassPair分配的所有类(和元类)的表。(已分配)
    
    if (!doneOnce) {
           doneOnce = YES;
           ......
        // 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);
    }
    
    // 2.读取所有类的列表
    for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
    }
    
    // 3.获取所有的类引用
    for (EACH_HEADER) {
        Class *classrefs = _getObjc2ClassRefs(hi, &count);
    }

    // 4.sel - 方法编号
    for (EACH_HEADER) {
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
    }
    
    // 5.修复旧的objc_msgSend_fixup调用导致一些消息没有处理
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
    }
    
    // 6.协议
    for (EACH_HEADER) {
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle);
        }
    }
    
    // 7.修复协议重映射
    // 获取所有的协议引用
    for (EACH_HEADER) {
       protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
       for (i = 0; i < count; i++) {
           remapProtocolRef(&protolist[i]);
       }
    }
    // 🌞Realize non-lazy classes (for +load methods and static instances)
    // 🌞8.实现非惰性类(用于+ load方法和静态实例)
     for (EACH_HEADER) {
         classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);
     }
    
    // 9.在CF基础上,实现未来类
    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);
            cls->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }
    
    
    // 10.分类
    for (EACH_HEADER) {
        category_t **catlist = _getObjc2CategoryList(hi, &count);
    }
}

下面我们依次对其中的内容进行分析

1. 创建两张表

if (!doneOnce) {
           doneOnce = YES;
           ......
        // 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);
    }

gdb_objc_realized_classes存储所有的类,包括已实现和未实现并且在dyld共享缓存中不存在的类,其容量是类数量的4/3 allocatedClasses存储通过objc_allocateClassPair已分配的所有类和元类的表

2. 读取所有的类

// 读取所有类的列表
    for (EACH_HEADER) {
        // 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
        // ClassList 可以通过MachoView查看在sesion段下
        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,Foundation,libdispatch中的类,以及自己创建的类
            // 注意: 此时cls还不是类,是地址
            Class cls = (Class)classlist[i];
            // 通过readClass函数获取处理后的新类,内部主要操作ro和rw结构体
            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也是重点,它的作用就是将类插入到表中。里面会对ro,rw进行相应的操作,但是需要注意的是:针对的是未来的类

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    ......
    Class replacing = nil;
    // 一般情况这里不会走进去,只会对未来的类进行处理
    if (Class newCls = popFutureNamedClass(mangledName)) {
        // This name was previously allocated as a future class.
        // Copy objc_class to future class's struct.
        // Preserve future's rw data block.
        
        if (newCls->isAnySwift()) {
            _objc_fatal("Can't complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro;
        memcpy(newCls, cls, sizeof(objc_class));
        rw->ro = (class_ro_t *)newCls->data();
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // assert(cls == getClass(name));
        assert(getClass(mangledName));
    } else {
        // 添加类
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

readClass中会判断是否是后期要处理的类,是的话就读取classdata(),进行rorw处理会调用addNamedClass的方法,并将类添加到总表当中,对应的代码是NXMapInsert(gdb_objc_realized_classes, name, cls)

/***********************************************************************
* 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 = getClass(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getNonMetaClass 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());
}

3. 对所有的类做重映射

**注意:**一般走不到这里

// 将未映射Class和Super Class重映射,被remap的类都是非懒加载的类
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // 重映射Class。注意从_getObjc2ClassRefs函数中取出类的引用
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

4. 添加SEL到namedSelectors表

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);
            }
        }
    }

sel_registerNameNoLock方法中会将SEL添加到内存,例如,我们写代码时Xcode能够进行自动提示就是因为这个。

5. 修复旧的函数指针调用遗留

for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            // 内部将常用的alloc,objc_msgsend等函数指针进行注册,并fix为新的函数指针
            fixupMessageRef(refs+i);
        }
    }

6. 将所有的协议添加到protocal_map表中

调用_getObjc2ProtocolList获取协议列表,遍历进行添加

for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        // cls = Person类,所有协议和对象的结构体都类似。isa都对应Protocal类
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        // 获取protocal哈希表
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

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

7. 对所有的协议做重映射

同样是通过遍历的方式进行重映射

for (EACH_HEADER) {
        // 注意,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolLists不一样,完全是两个不同的调用
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

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

// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            // hack for class __ARCLite__, which didn't 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
            
            addClassTableEntry(cls);
            // 实现所以非懒加载的类(实例化类对象的一些信息,比如rw)
            realizeClass(cls);
        }
    }

看到此处,恭喜你,其中realizeClass将是我们本篇章的核心。 需要注意的是在起始位置系统有句注释:Realize non-lazy classes (for +load methods and static instances),这里有个概念:非懒加载类和懒加载类,官方给出的解释如下:

NonlazyClass is all about a class implementing or not a +load method.

意思是说要实现一个非懒加载的类,需要在类内部实现+load方法,那么实现了+load方法的类是非懒加载类(下面分析懒加载类的时候会加以验证),否则是懒加载类。明白了这两者的区别,下面我们来看下非懒加载类的加载流程。

8.1 realizeClass

由于代码过长,去掉了部分系统注释,更详细的代码大家可以自行查看源码

/***********************************************************************
* realizeClass
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{
    ......省略部分
    if (!cls) return nil; // 🌞递归调用的出口
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));
    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);
        // 🌞注意:此时只是初始化 rw 里面还没有内容
    }

    isMeta = ro->flags & RO_META;
    rw->version = isMeta ? 7 : 0;  // old runtime went up to 6

    cls->chooseClassArrayIndex();

    ......省略部分

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // 🌞递归处理superClass和meta
    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()));

    ......省略部分

    // Update superclass and metaclass in case of remapping
    // 🌞父类与元类归属关系的处理
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    // Connect this class to its superclass's subclass lists
    // 🌞将此类连接到其超类的子类列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    // 🌞将ro数据写入rw
    methodizeClass(cls);

    return cls;
}

总结来说在realizeClass做了这么几件事:

  1. 读取classdata(),并进行ro/rw的创建
  2. 递归调用realizeClass,进行父类和元类的实现
  3. 父类与元类归属关系的处理
  4. 将此类连接到其超类的子类列表
  5. 调用methodizeClass,将ro数据写入rw

8.2 methodizeClass

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();
    bool isMeta = cls->isMetaClass();
    // 🌞读取到rw,此时rw还是没有值的
    auto rw = cls->data();
    auto ro = rw->ro;
    ......
    // 🌞 读取 ro 的baseMethods,baseProperties,baseProtocols并调用
    attachLists 将他们附加到 rw 当中
    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);
    }

    // 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, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
    ......省略
}

methodizeClass中,会获取到rw,并读取robaseMethodsbasePropertiesbaseProtocols并调用attachLists 将他们附加到 rw 当中。

8.3 attachLists

attachLists处理了附加的逻辑

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;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            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]));
        }
    }

可以看出,此处也分三种情况处理:

  1. 多对多 many lists -> many lists

通过realloc对容器进行重新分配大小为原来的大小加上新增的大小 通过memmove把原来的数据移动到容器的末尾 通过memcpy把新的数据拷贝到容器的起始位置

  1. 0对1 0 lists -> 1 list

调用addedLists附加到第一个位置

  1. 1对多 1 list -> many lists

通过realloc对容器进行重新分配大小为原来的大小加上新增的大小 由于只有一个一维数组,所以直接赋值到新Array的最后一个位置 把新的数据memcpy拷贝到容器的起始位置

9. 遍历已标记的懒加载类,并进行初始化操作

if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            // 实现懒加载的类
            realizeClass(resolvedFutureClasses[i]);
            resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    } 

realizeClass会处理懒加载的类,暂时先不展开,下个篇章将会进行分析

10. 处理所有的类别,包括class和metaClass

以下是精简后的代码,并已经加上相应注释

  for (EACH_HEADER) {
        // 外部循环遍历找到当前类,查找类对应的Category数组
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            // 内部循环遍历当前类的所有Category
            category_t *cat = catlist[I];
            Class cls = remapClass(cat->cls);
            
            // 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                // 将Category添加到对应Class的value中,value是Class对应的所有category数组
                addUnattachedCategoryForClass(cat, cls, hi);
                // 将Category的method、protocol、property添加到Class
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            // 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
            // 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
            }
        }
    }

至此,map_images已经分析完毕

11. _read_images小结

总结来说,在_read_images中做了以下几件事情:

  1. 加载所以的类到类的gdb_objc_realized_classes表中
  2. 对所有的类做重映射
  3. 将所有的SEL注册到哈希表中
  4. 修复函数指针遗留
  5. 将所有的协议添加到protocal_map表中
  6. 对所有的协议做重映射
  7. 初始化所有非懒加载类,进行rw,ro的操作
    🌞realizeClass: 读取classdata(),并进行ro/rw的创建。递归调用realizeClass,进行父类和元类的实现。父类与元类归属关系的处理。将此类连接到其超类的子类列表。调用methodizeClass,将ro数据写入rw
    🌞methodizeClass: 获取到rw,并读取robaseMethodsbasePropertiesbaseProtocols并调用attachLists 将他们附加到 rw 当中。
  8. 遍历已标记的懒加载类,并进行初始化操作
  9. 处理所有的类别,包括class和metaClass
  10. 初始化所有未初始化的类

三:懒加载类

在上面的内容当中我们对normal class即非懒加载类的加载过程进行了分析,那么future class即懒加载类又是如何进行加载的呢?

1. 懒加载类的加载过程

按照常理来分析,懒加载必然是用到的时候才会去进行加载,什么时候会用到,那就是向这个类发送消息的时候。当我们创建一个对象的时候,会调用alloc去开辟内存空间,在整个过程中必然会来到一个方法 --> lookUpImpOrForward,翻看源码,我们发现其中有这么一句:

if (!cls->isRealized()) {
        realizeClass(cls);
    }

这里判断如果没有加载好,那就先加载一下类信息,而且realizeClass是否似曾相识,它不就是上个篇章中的核心么。非懒加载就是在其中进行加载的,这正好符合懒加载类在使用的时候才去初始化的特性。
发现不同版本的源码不一样,我用的是objc4-750,最新的源码可能方法名有所不同,最新的源码调用的是realizeClassWithoutSwift

在上面非懒加载类的加载过程中我们也已经分析过realizeClass:

    1. 读取classdata(),并进行ro/rw的创建
    1. 递归调用realizeClass,进行父类和元类的实现
    1. 父类与元类归属关系的处理
    1. 将此类连接到其超类的子类列表
    1. 调用methodizeClass,将ro数据写入rw

通过分析我们可以得出这样的结论:懒加载类是第一次向类发送消息的时候加载到内存的

2. 实现+load会使懒加载类的加载提前

此时懒加载类会变成非懒加载类,下面我们用代码加以验证。首先看下我们的测试代码。共有WYPersonWYWorkerWYTeacher三个类,WYPerson未实现+load方法,后两者皆实现+load方法

我们来到read_image中的处理非懒加载类的地方,打印出所有非懒加载的类

for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            // 🐵🌹打印所有的非懒加载类
            printf("_getObjc2NonlazyClassList Class:%s\n",cls->mangledName());
            
            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
            }
            // 实现所有非懒加载的类
            realizeClassWithoutSwift(cls, nil);
        }
    }

通过如图的打印,验证了我们的结论:当一个懒加载的类实现了+load方法,它的加载将会提前,变成一个非懒加载的类

四:总结

  1. 非懒加载类是在runtime启动时map_images方法执行期间就已经初始化完成;
  2. 懒加载类是在其第一次使用,也就是第一次发送消息时对类进行初始化的时候加载到内存的。
  3. +load方法会使懒加载类的加载提前。

下一篇章会对分类进行相应的分析,敬请期待!