类的加载(一)

在上一篇文章(dyld和objc的关联)中,
我们我已经了解到objc的初始化方法_objc_init,本文的主题便是由_objc_init开始,一步步探究类是如何从Mach中加载到内存的。

_objc_init的实现内容

我们贴上_objc_init的实现代码。(代码来源objc-7.8.1)

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();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    // 什么时候调用? images 镜像文件
    // map_images()
    // load_images()
    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

根据注释和代码,这个方法中每一个方法调用实现的内容为:

  1. environ_init(): 读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助
  2. tls_init(): 关于线程key的绑定---比如每线程数据的析构函数
  3. static_init(): 运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc会调用_objc_init(),因此我们必须自己做
  4. lock_init(): 没有重写,采用C++的特性
  5. exception_init(): 初始化libobjc的异常处理系统
  6. runtime_init(): runtime运行时环境初始化,里面是unattachedCategoriesallocatedClasses
  7. exception_init(): 缓存条件初始化
  8. _imp_implementationWithBlock_init(): 启动回调机制,通过这不会做什么,因为所有的初始化都是惰性的,都是对于某些进程,我们会迫不及待地加载trampolines dylib
  9. _dyld_objc_notify_register: 在dyld中注册回调函数

在这里没有看到有关类的初始化的相关代码,那么实现的代码可能就在注册在dyld中的回调函数中,我们在上一篇文章已经知道load_images中时调用类的load方法,并没有类的初始化相关代码,那么类的初始化就有可能在map_images中实现。(也可以顾名思义,我们从Mach-O加载内存实际上是加载到一张map)

map_images的调用时机

map_images是注册到dyld的回调方法,那么dyld是在何时调用的呢,我们通过dyld代码一步步探究。

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;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    //  call 'init' function on all images already init'ed (below libSystem)
    for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

map_images赋值给了sNotifyObjCMapped,全局搜索sNotifyObjCMapped,在notifyBatchPartial找到调用

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
    ...
    if ( objcImageCount != 0 ) {
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
        uint64_t t0 = mach_absolute_time();
        (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
        uint64_t t1 = mach_absolute_time();
        ImageLoader::fgTotalObjCSetupTime += (t1-t0);
    }
    ...
}

全局搜索notifyBatchPartial,可以看到registerObjCNotifiers,也就是注册回调函数的时候,便会调用一遍这个方法,所以,当map_images注册到dyld时,dyld便会尝试调用一遍这个方法,而map_images的执行肯定在load_images之前。

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;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    //  call 'init' function on all images already init'ed (below libSystem)
    for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

map_images的调用流程

进入map_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方法,

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

_read_images的代码较多,下面贴上简略的代码。

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // 只执行一次
    if (!doneOnce) {
    ...
    }

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

    bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();

    // 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. rdar://problem/5311914r5
    if (didInitialAttachCategories) {
        ...
    }

    ts.log("IMAGE TIMES: discover categories");

    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        ...
    }

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

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    // Print preoptimization statistics
    if (PrintPreopt) {
        ...
    }

#undef EACH_HEADER
}

这个方法代码也是不少,总结一下它做了以下事情

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

忽视分类以及协议,我们重点研究我们自定义的类加载的过程。

NXCreateMapTable -- 创建map保存类信息

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

这里创建这里一个大小为总类数量4/3的表,用来保存所有类型的信息

readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName  = cls->mangledName();

    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    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->set_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(getClassExceptSomeSwift(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;
}

这个方法看起来像真正进行类的加载的方法,可以看到这里有对类bits的ro和rw进行赋值的过程,然而我们通过断点调试,发现程序并没有进入到ro和rw的赋值部分,实际这个方法中只是简单地存储了类的地址和名字。

这个方法具体执行了那些操作呢,我们通过addNamedClassaddClassTableEntry这两个方法来一窥究竟。

  1. addNamedClass
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());
}

程序会来到NXMapInsert,在这个方法里会将类添加到一开始创建的gdb_objc_realized_classes

  1. addClassTableEntry
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.
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

这个方法里,类会被添加到objc的allocatedClasses这个集合里,allocatedClasses便是我们一开始runtime_init时便已经创建好的集合,另外这个方法里还会通过类的isa找到类的元类,将当前类的元类也添加到这个集合中。

经过readClass后,类便从Mach-O读取到内存,也就是一张表中,但是目前内存中的类仅有地址和名字这两个数据,而Mach-O中的数据仍未读取。

realizeClassWithoutSwift

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    // data() -> ro
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // 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.
    // cls 信息 -> 父类 -> 元类 : cls LGPerson
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    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();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
    
    // Attach categories - 分类 
    methodizeClass(cls, previously);

    return cls;
}

这个方法便是将类的信息完全加载出来的的方法,在_read_images中有两处对该方法调用,而通过断点调试我们发现调用的是第一个realizeClassWithoutSwift进行类的加载。

一般来说,后面凡是带“Future”字样的条件执行内容我们都不必进行探究,通过断点调试发现,程序并不会进入到Future的内容。

为了针对性研究自定义类,我们创建一个类并实现他的load方法(因为没有实现load方法的类为懒加载的类,并不会在mian函数之前初始化), 并且在realizeClassWithoutSwift开始的地方添加一段代码打上断点,通过断点调试探究。

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;

- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;

+ (void)kc_sayClassMethod;

@end

...

#import "LGPerson.h"

@implementation LGPerson

+ (void)load{
}

- (void)kc_instanceMethod2{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod3{
    NSLog(@"%s",__func__);
}

+ (void)kc_sayClassMethod{
    NSLog(@"%s",__func__);
}
@end
4A73ED7F-6B54-4437-A685-A4A885A8C0D2.png

运行程序,往下执行断点来到

299E324D-0D52-4C87-857A-4AD234EAFBFF.png

这个过程中,Mach-O中类的data被直接赋值给了roro在赋值给了rw,之后在将rw赋值给clsbits,而rw中除了ro,还有一个rwe,如果rwe有数据,那么ro会复制一份存放在rwe中。

我们可以rwro的get方法得出以上结论

    // 外界读取ro的方法
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        // 如果rwe中有数据,返回rwe中的ro
        if (slowpath(v.is())) {
            return v.get()->ro;
        }
        // rwe没有数据,返回rw中的ro
        return v.get();
    }

ro表示readonly,指类的clean Menory,这块内存初始化完成后便不会被修改,rwe一块dirty Menory,这块区域中的数据会被修改,在苹果WWDC-2020的内存优化说明中,对这一块有详细的介绍。

处理好ro和rw后,方法来到了递归调用,这里会通过clssuperclassisa找到父类和元类对象,通过递归调用realizeClassWithoutSwift初始化,从而确定类和元类的继承链。

7836BE22-63F6-4552-8EA2-8647905B52A1.png

确定好继承链后便是一系列isa处理后,方法来到了methodizeClass,这个方法做了什么呢?我们进入探究。

methodizeClass

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

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

同样为了针对性研究,我们同样在methodizeClass方法中添加代码研究。

D2C81131-3ACE-41F8-9610-6DC05F0ACACD.png

首先来到是方法的处理,这里拿到robaseMethod,如果有值,则执行prepareMethodLists处理方法列表,如果rwe有值,则通过调用methodsattachLists将处理后的方法列表添加到rwe。中,

因为本文探讨的类没有添加分类,所以rwe为空,有关rwe的内容下一篇文章会做探讨,本文不做探究

7599D2B2-2F11-4689-86C3-3CCD40C077DA.png

打印list的内容,可以看到里面存放是类的所有实例方法信息

(lldb) p *list
(method_list_t) $0 = {
  entsize_list_tt = {
    entsizeAndFlags = 24
    count = 8
    first = {
      name = "kc_instanceMethod2"
      types = 0x0000000100000eb8 "v16@0:8"
      imp = 0x0000000100000c50 (KCObjc`-[LGPerson kc_instanceMethod2])
    }
  }
}
(lldb) p $0.get(0)
(method_t) $2 = {
  name = "kc_instanceMethod2"
  types = 0x0000000100000eb8 "v16@0:8"
  imp = 0x0000000100000c50 (KCObjc`-[LGPerson kc_instanceMethod2])
}
(lldb) p $0.get(1)
(method_t) $3 = {
  name = "kc_instanceMethod1"
  types = 0x0000000100000eb8 "v16@0:8"
  imp = 0x0000000100000c80 (KCObjc`-[LGPerson kc_instanceMethod1])
}
(lldb) p $0.get(2)
(method_t) $4 = {
  name = "kc_instanceMethod3"
  types = 0x0000000100000eb8 "v16@0:8"
  imp = 0x0000000100000cb0 (KCObjc`-[LGPerson kc_instanceMethod3])
}
(lldb) p $0.get(3)
(method_t) $5 = {
  name = "kc_name"
  types = 0x0000000100000ece "@16@0:8"
  imp = 0x0000000100000ce0 (KCObjc`-[LGPerson kc_name])
}
(lldb) p $0.get(4)
(method_t) $6 = {
  name = "setKc_name:"
  types = 0x0000000100000ed6 "v24@0:8@16"
  imp = 0x0000000100000d10 (KCObjc`-[LGPerson setKc_name:])
}
(lldb) p $0.get(5)
(method_t) $7 = {
  name = "kc_age"
  types = 0x0000000100000ee1 "i16@0:8"
  imp = 0x0000000100000d40 (KCObjc`-[LGPerson kc_age])
}
(lldb) p $0.get(6)
(method_t) $8 = {
  name = "setKc_age:"
  types = 0x0000000100000ee9 "v20@0:8i16"
  imp = 0x0000000100000d60 (KCObjc`-[LGPerson setKc_age:])
}
(lldb) p $0.get(7)
(method_t) $9 = {
  name = ".cxx_destruct"
  types = 0x0000000100000eb8 "v16@0:8"
  imp = 0x0000000100000d80 (KCObjc`-[LGPerson .cxx_destruct])
}

prepareMethodLists中做了哪些事情呢?我们进入方法中查看

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
    runtimeLock.assertLocked();

    if (addedCount == 0) return;

    // There exist RR/AWZ/Core special cases for some class's base methods.
    // But this code should never need to scan base methods for RR/AWZ/Core:
    // default RR/AWZ/Core cannot be set before setInitialized().
    // Therefore we need not handle any special cases here.
    if (baseMethods) {
        ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
    }

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }

    // If the class is initialized, then scan for method implementations
    // tracked by the class's flags. If it's not initialized yet,
    // then objc_class::setInitialized() will take care of it.
    if (cls->isInitialized()) {
        objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
    }
}

除去一些断言和赋值操作,我们看一个关键的方法fixupMethodList,这个方法中便对方法进行了排序,这也就是为什么我们在方法慢速查找时可以用二分查找。

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            meth.name = sel_registerNameNoLock(name, bundleCopy);
        }
    }

    // Sort by selector address.
    if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}

可以看到方法的排序是根据SEL的地址从低到高排序的。

方法处理完成后,类后面还会进行协议和属性的处理,至此,类的加载已基本完成。但我们目前探究的是非懒加载的类,那么懒加载的类又是何时加载的呢?

懒加载类的加载

大多数情况下,我们所创建的类都是懒加载的类(即类没有实现load方法),如果我们没有给这个类发送任何消息,那么这个类永远不会被加载。为了探究它是何时被加载进内存的,我们可以在已经确定的类的加载关键方法realizeClassWithoutSwift里打上断点,并给当前类发送消息,看看他是何时加载进内存的。

LGPerson *person = [LGPerson alloc];
[person kc_instanceMethod1];

进入断点后,用lldb打印调用堆栈

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
    frame #0: 0x00000001002de6c6 libobjc.A.dylib`realizeClassWithoutSwift(cls=LGPerson, previously=0x0000000000000000) at objc-runtime-new.mm:2553:66
    frame #1: 0x00000001002fb33a libobjc.A.dylib`realizeClassMaybeSwiftMaybeRelock(cls=LGPerson, lock=0x0000000100337000, leaveLocked=false) at objc-runtime-new.mm:2833:9
    frame #2: 0x00000001002e98cf libobjc.A.dylib`realizeClassMaybeSwiftAndUnlock(cls=LGPerson, lock=0x0000000100337000) at objc-runtime-new.mm:2850:12
    frame #3: 0x00000001002ddff0 libobjc.A.dylib`initializeAndMaybeRelock(cls=0x0000000100002240, inst=0x0000000100002268, lock=0x0000000100337000, leaveLocked=true) at objc-runtime-new.mm:2162:19
    frame #4: 0x00000001002e695a libobjc.A.dylib`initializeAndLeaveLocked(cls=0x0000000100002240, obj=0x0000000100002268, lock=0x0000000100337000) at objc-runtime-new.mm:2187:12
  * frame #5: 0x00000001002e66a1 libobjc.A.dylib`lookUpImpOrForward(inst=0x0000000100002268, sel="alloc", cls=0x0000000100002240, behavior=3) at objc-runtime-new.mm:6257:15
    frame #6: 0x00000001002c1bd9 libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1101
    frame #7: 0x000000010031d0c5 libobjc.A.dylib`objc_alloc [inlined] callAlloc(cls=LGPerson, checkNil=true, allocWithZone=false) at NSObject.mm:1714:12
    frame #8: 0x000000010031d01e libobjc.A.dylib`objc_alloc(cls=LGPerson) at NSObject.mm:1730
    frame #9: 0x0000000100000bdb KCObjc`main(argc=1, argv=0x00007ffeefbff4b0) at main.m:56:28 [opt]
    frame #10: 0x00007fff717dacc9 libdyld.dylib`start + 1

可以看到,懒加载的类,直到我们调用第一次调用方法时,经过慢速查找lookUpImpOrForward查找方法时,方向当前类并没有初始化,便会通过realizeClassMaybeSwiftAndUnlock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift,从而将类加载到内存中。

总结

综上,类的加载流程可以由下图表示

类的加载.png

你可能感兴趣的:(类的加载(一))