美文网首页
类的加载(一)

类的加载(一)

作者: iOSer_jia | 来源:发表于2020-10-26 02:03 被阅读0次

    在上一篇文章(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
        }
    
        // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
        for (std::vector<ImageLoader*>::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
        }
    
        // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
        for (std::vector<ImageLoader*>::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<class_rw_t>();
            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<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>()->ro;
            }
            // rwe没有数据,返回rw中的ro
            return v.get<const class_ro_t *>();
        }
    

    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<method_t, method_list_t, 3> = {
        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

    相关文章

      网友评论

          本文标题:类的加载(一)

          本文链接:https://www.haomeiwen.com/subject/movomktx.html