阅读 518

iOS 开发 — 类的加载

在前几篇文章中我们研究了对象、类和方法,这次我们就来研究一下在开发中非常重要的类究竟是如何加载的。

类的加载流程

我们从_objc_init函数开始看起,其实在这之前还包括dyld对动态库的加载、链接等一系列操作,然后才会来到_objc_init函数,这个过程我们日后再另出文章研究。

_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);
}
复制代码

可以看来这里面调用了多个函数,我们来依次解析一下:

1. environ_init

这个函数的作用主要是读取影响运行时的环境变量,如果需要还可以打印环境变量,在其内部有这样一段代码:

// 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);
        }
    }
复制代码

如果我们把for循环拿到上面去,就会得到一些系统环境变量的信息:

objc[15152]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[15152]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[15152]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[15152]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[15152]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[15152]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[15152]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[15152]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[15152]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[15152]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[15152]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[15152]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[15152]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[15152]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[15152]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[15152]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[15152]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[15152]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[15152]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[15152]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[15152]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[15152]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[15152]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
复制代码

我们可以通过改变环境变量的值来达到调试的目的。

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

/***********************************************************************
* 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]();
    }
}
复制代码

这个函数作用是运行C++静态构造函数,在dylb调用我们的静态构造函数之前libc就会调用_objc_init,所以需要自己实现。

4. lock_init

void lock_init(void)
{
}
复制代码

这个里面是空实现,所以我们也不知道里面究竟做了什么。

5. exception_init

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

这个函数主要是初始化libobjc的异常处理系统,注册相应的监听回调机制,从而监控异常。

6. _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镜像文件被映射、取消映射、初始化的时候注册处理程序。
  • dyld将会通过一个包含objc-image-info的镜像文件的数组回调给mapped函数。
  • 在调用dyld_objc_notify_register期间,dyld将调用mapped函数来使用已加载的objc镜像文件。
  • 当dyld调用initializers的时候会调用init函数。

从这一系列流程我们可以得知,前面几个函数基本都是准备条件,这最后一个函数_dyld_objc_notify_register才是加载类的开始。

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
复制代码

我们看到_dyld_objc_notify_register有三个参数,map_images在image加载到内存的时候会触发,load_images在初始化image的时候会触发,unmap_image在移除image的时候触发。

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函数里调用了map_images_nolock函数,map_images_nolock里调用了_read_images函数,这个函数正是map_images里的核心内容。

_read_images

_read_images函数的代码很多,我们需要分段来进行研究。

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);
        
        ts.log("IMAGE TIMES: first time tasks");
    }

复制代码

变量doneOnce控制了这段代码只会执行一次,首先创建了两张表,gdb_objc_realized_classes这张表存储了不在dyld共享缓存里的所有的类,包括已经实现的和没实现的,其容量是所有类数量的4/3。allocatedClasses这张表只存储已经初始化的类。这么做的目的猜测是在使用的时候只带着allocatedClasses这张小表就好了,效率也高。

2. 类的重映射

// 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函数获取处理后的新类,
            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;
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");
复制代码

这里的重点在于readClass这个函数,在这个函数中可以看到如下代码:

    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;
    }
复制代码

乍一看好像在这里会进行ro的读取和rw的赋值,但其实如果我们这个判断条件上打个断点会发现程序跟不会走到这里,也就是说一般的系统类和自定义类并不会走这里,只有符合popFutureNamedClass条件的类才会走这里。接着往下看:

addNamedClass(cls, mangledName, replacing);
addClassTableEntry(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 = 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());
}
复制代码

addNamedClass作用是将当前类插入到总表gdb_objc_realized_classes中。

/***********************************************************************
* 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);
}
复制代码

addClassTableEntry作用是将当前类插入到allocatedClasses这张表中。

readClass后我们会拿到一个newCls,用它来和cls做比较,不同的话就会做一些特殊处理,但在实际调试的过程中并没有走,在readClass中我们知道只有符合popFutureNamedClass条件的类才会走特殊处理,走了才会导致newCls和cls不一样,所以这里的符合条件也是一样的,一般的系统类和自定义类并不会走。

接下来是修复重映射,不过一般走不进来,暂时也不用过多关注。

    // 主要是修复重映射 - 一般走不进来
    // 将未映射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]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");
复制代码

3. 将SEL添加到namedSelectors表中

    // 将所有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);
            }
        }
    }

    ts.log("IMAGE TIMES: fix up selector references");
复制代码

再来看一下sel_registerNameNoLock的源码:

static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) 
{
    SEL result = 0;

    if (shouldLock) selLock.assertUnlocked();
    else selLock.assertLocked();

    if (!name) return (SEL)0;

    result = search_builtins(name);
    if (result) return result;
    
    conditional_mutex_locker_t lock(selLock, shouldLock);
    if (namedSelectors) {
        result = (SEL)NXMapGet(namedSelectors, name);
    }
    if (result) return result;

    // No match. Insert.

    if (!namedSelectors) {
        namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, 
                                          (unsigned)SelrefCount);
    }
    if (!result) {
        result = sel_alloc(name, copy);
        // fixme choose a better container (hash not map for starters)
        NXMapInsert(namedSelectors, sel_getName(result), result);
    }

    return result;
}
复制代码

这部分代码比较好懂,其实就是从Macho文件的数据段中读出所有的SEL,再将所有SEL插入到namedSelectors表中。

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

    // Fix up old objc_msgSend_fixup call sites
    // 修复旧的函数指针调用遗留
    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);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
复制代码

这一部分也不是重点,只做了解。

5. 将所有协议添加到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);
        }
    }

    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.
    // 修复协议列表引用,优化后的images可能是正确的,但是并不确定
    for (EACH_HEADER) {
        // 需要注意到是,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不一样
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

    ts.log("IMAGE TIMES: fix up @protocol references");
复制代码

这一部分是将所有的协议都添加到protocol_map表中。然后会对协议列表的协议引用进行修复。

6. 初始化所有的非懒加载类

首先我们要知道懒加载类与非懒加载类的区别,根据苹果官方文档的解释: 两者之间的主要区别在于是否实现了+load方法,实现了+load方法则为非懒加载类,没有实现则为懒加载类。 接下来会遍历调用realizeClassWithoutSwift来实现所有非懒加载的类。

6.1 读取类的数据

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);
    }
复制代码

首先程序会读取类的data信息获取到ro,ro是一个只读的结构,在编译期就已经赋值了,主要存储了类的实例变量、属性列表、方法列表和协议列表等信息,在这一步rw只是进行了初始化,还并未有赋值操作。

6.2 递归实现父类和元类

    // 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.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift’s initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
复制代码

之前我们在探索类的结构时讲过,类结构中包含isasuperclass,这里正是利用这一点去递归实现类的元类和父类,以保证类的继承链的完整性。至于递归的出口,我们知道所有类的基类是NSObject,而NSObject的父类是nil,所以递归到nil就会跳出去。而元类不同,类通过isa会找到元类,接着找到根元类,而根元类的元类指向自己,这样会进入死循环,不过苹果肯定是做的很完善的,在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;
    }
}
复制代码

这里其实是类的查找,如果在表里已经存在该类就会返回c2,其实也就是nil,从而跳出元类的递归实现。

6.3 将此类连接到其父类的子类列表

// Connect this class to its superclass’s subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
复制代码

这一步的目的将当前类添加到其父类的子类列表中,使得子类与父类形成一个类似于双向链表的结构。

6.4 对rw进行赋值

经过一系列处理在函数的最后会调用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);
    }
    
    // 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*/);
复制代码

我们可以看到不管是方法、属性还是协议都是通过attachLists来装载到rw中的,那么我们就有必要来看一下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;
            /**
            *   从`array()->lists`所指的内存区域的起始位置拷贝
            *   `oldCount * sizeof(array()->lists[0])`个字节到
            *   `array()->lists + addedCount`所指的内存区域。
            *   可以避免因为两块内存有重叠区域而被覆盖
            */
            memmove(array()->lists + addedCount, array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            /**
            *   从`addedLists`所指的内存区域的起始位置拷贝
            *   `addedCount * sizeof(array()->lists[0])`个字节到
            *   `array()->lists + addedCount`所指的内存区域。
            *   无法避免因为两块内存有重叠区域导致的内存被覆盖问题
            *   使用时必须确保两块内存没有重叠部分
            *   效率上比`memmove`要高一些
            */
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            // 原来没有元素并且新增元素个数为1
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            // 原来有1个元素并且新增元素个数为复数
            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]));
        }
    }
复制代码

这个函数的作用其实是在原有的数组上做扩容操作,然后把原有的元素向后移,新增的元素插入到最前面。由此可知所谓的分类会覆盖类中的同名方法是一个假象,其实两个方法是同时存在的,只是分类的方法在前面,因为方法查找是按顺序查找的,所以调用的是分类的方法。

7. 发现和处理所有Category

// Discover categories.
    // 发现和处理所有Category
    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);

            if (!cls) {
                // Category’s target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class’s method lists (etc) if 
            // the class is realized.
            // 首先,通过其所属的类注册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;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

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

    ts.log("IMAGE TIMES: discover categories");
复制代码

至此,一个非懒加载类的加载过程基本就完成了。

懒加载类的加载流程

其实大部分开发者都知道懒加载类是在调用的时候才会去初始化的,只不过是没有深入探索过具体流程,这次我们顺便就探索一下。

既然我们知道懒加载类在使用的时候才会初始化,类创建对象又是通过alloc方法来进行,而方法的本质就是消息发送,所以我们就需要去到一个消息发送流程中很重要的函数lookUpImpOrForward,在这个函数内部有这样一段代码:

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

这里的判断条件是是否已经实现过,没实现过才符合条件。如果条件判断成立,就会调用realizeClassMaybeSwiftAndLeaveLocked,而realizeClassMaybeSwiftAndLeaveLocked内部又调用了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;
}
复制代码

从源码中我们可以发现程序最终会调用realizeClassWithoutSwift,而该函数的内容正是我们前面已经分析过的类的加载流程,由此我们可以确定,懒加载类是在第一次被调用的时候才会开始加载到内存的

load_images

我们知道+load方法是区分懒加载类和非懒加载类的重要方法,那么+load是怎么调起的呢,这就需要我们研究一下上面提到过的_dyld_objc_notify_register的第二个参数load_images

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
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
复制代码

从源码中我们可以看到有两个重要的函数:prepare_load_methodscall_load_methods

prepare_load_methods

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);
    }
}
复制代码

这个函数主要是对非懒加载类和非懒加载分类进行处理。

对非懒加载类的处理:

  • 首先通过_getObjc2NonlazyClassList获取所有的非懒加载类的集合。
  • 然后遍历集合并调用schedule_class_load,此函数会递归寻找没有加载+load方法的父类并调用add_class_to_loadable_list将父类和当前类及其对应的+load方法加入列表,注意是先父类后子类。
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); 
}
复制代码

对非懒加载分类的处理:

  • 首先通过_getObjc2NonlazyCategoryList获取所有的非懒加载类的集合。
  • 遍历集合,通过分类获取到主类,调用realizeClassWithoutSwift将主类初始化。
  • 调用add_category_to_loadable_list将分类及其对应的+load方法加入到列表中。

call_load_methods

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) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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;
}
复制代码

call_load_methods内部实现了所有类的+load方法的调用。通过一个do-while循环不断遍历调用类和分类的+load方法,call_class_loads函数内实现了对类的+load方法的调用,call_category_loads函数内实现了对分类的+load方法的调用。同时从代码的执行顺序我们也能看出来,+load方法的调用顺序是先主类后分类。

类与分类搭配加载分析

前面讲完了类的加载流程,其中也包括了分类。跟主类一样分类也存在懒加载和非懒加载的情况,所以类和分类搭配加载就会存在四种情况:

1. 非懒加载类 + 非懒加载分类

这种情况是类和分类都实现了+load方法。就是正常的先加载类,再加载分类,属于比较好理解的一种情况。

2. 懒加载类 + 非懒加载分类

这种情况是类中没有实现+load方法而分类中实现了+load方法。通过前面的研究我们知道懒加载类是在第一次发送消息的时候才会加载,而非懒加载分类在read_image中就加载了,这就造成了一个问题,分类已经加载了而类却没有,当然苹果已经给出了解决方案,就在前面我们研究load_image中有一个prepare_load_methods函数,这个函数我们已经知道处理非懒加载分类的时候会把对应的主类初始化,所以在这种情况下懒加载类的加载就不是在第一次发送消息的时候而是提前到load_image中的prepare_load_methods里。

3. 非懒加载类 + 懒加载分类

这种情况是类中实现了+load方法而分类中没有实现。这里要说明的一点是:**分类的懒加载不同于类,分类的懒加载是编译时就已经加载完成。**所以这种情况就是类会走正常的加载流程read_images -> realizeClassWithoutSwift -> methodlizeClass,在添加分类的时候直接从data()->ro里拿就行了。

4. 懒加载类 + 懒加载分类

这种情况是类和分类都没有实现+load方法。和上面那种情况区别只在于类的加载时机不一样,分类是一样的。类会在第一次发送消息的时候加载,走方法查找的流程消息发送 -> lookuporforward -> realizeClassWithoutSwift -> methodlizeClass,同样在需要添加分类的时候直接从data()->ro里拿就行了。

总结

本篇文章我们详细探索了类的加载(包括非懒加载类和懒加载类)、分类的加载以及类和分类搭配加载的不同情况,流程已经比较清楚,本人能力有限,如有错误还请指正。

关注下面的标签,发现更多相似文章
评论