类的加载

课程复习:
上一节讲解了dyld,我们知道dyld是用来链接库的,为什么链接库呢,因为苹果开发人员也很多,链接就是将各个开发人员开发的库通过动态库形式全部链接到项目中来,因为systerm是系统级别的库,所以最先加载,然后通过_dyld_objc_notify_register将所有镜像文件里面的内容加载到内存中来

_dyld_objc_notify_register是将所有镜像文件映射出来,但是镜像文件是在dyld中,objc_init是在objc库里面,所有需要_dyld_objc_notify_register将所有就镜像文件的指针映射到objc库里

下面看看我们如何将镜像文件给加载到内存当中,以表的形式存储起来

一、_objc_init

上节课我们进入到_objc_init函数里面,我们来看看源码:

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();
    //关于线程key的绑定--比如每线程数据的析构函数
    tls_init();
    //运行系统的C++静态构造函数,在dyld调用我们的静态构造函数之前,libc会调用_objc_init(),所以我们必须自己做
    static_init();
    //无源码,就是说objc的异常完全才有c++那一套
    lock_init();
    //初始化异常处理系统,比如注册异常的回调函数,来监控异常
    exception_init();
    //仅供objc运行时使用,注册处理程序,以便在映射、取消映射和初始化objc镜像文件时调用
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

我们看到里面做了很多操作,下面让我们来一点点分析

1、环境变量的初始化-environ_init()

源码

void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() a lot.
    // This optimizes the case where none are set.
    for (char **p = *_NSGetEnviron(); *p != nil; p++) {
        if (0 == strncmp(*p, "Malloc", 6)  ||  0 == strncmp(*p, "DYLD", 4)  ||  
            0 == strncmp(*p, "NSZombiesEnabled", 16))
        {
            maybeMallocDebugging = true;
        }

        if (0 != strncmp(*p, "OBJC_", 5)) continue;
        
        if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
            PrintHelp = true;
            continue;
        }
        if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
            PrintOptions = true;
            continue;
        }
        
        const char *value = strchr(*p, '=');
        if (!*value) continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0 == strncmp(*p, opt->env, opt->envlen))
            {
                *opt->var = (0 == strcmp(value, "YES"));
                break;
            }
        }            
    }

    // Special case: enable some autorelease pool debugging 
    // when some malloc debugging is enabled 
    // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging")
             || getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc")))
            &&
            (!pooldebug || 0 == strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true;
        }
    }

    // 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
循环代码是在满足一定条件下将环境变量内容、环境变量的注释、环境变量是否已设置这些内容全部打印出来,但是这部分全是打印代码,为啥会在这里呢,因为苹果开发人员也是需要调试的。

那我们可以做一个操作,将打印的这部分代码移到条件外,并且让打印的条件满足,我们就可以看到打印的内容:

image.png

运行,我们发现打印了很多环境变量:

image.png

2、举例:OBJC_DISABLE_NONPOINTER_ISA

我们知道对象的isa是指向类的,但isa是一个联合体,里面有两个属性一个class,一个bits,并且两个属性是互斥的。正常情况下isa保存类信息是通过一些优化的,我们可以通过改变一下环境变量让系统不对isa做优化

image.png

我们发现对象的isa跟类的地址是不一样的,那我们通过修改环境变量让他不做优化,如下:

image.png

然后运行


image.png

发现对象的isa和类的地址完全相同了

举例:OBJC_PRINT_LOAD_METHODS

我们再改一下这个环境变量

image.png

然后运行

image.png

会发现有很多load,这个环境变量就是打印出所有实现load方法的类,不管系统的还是自己创建的,这样可以通过load去优化项目的启动速度

想看系统环境变量的功能,可以在终端输入:

export OBJC_HELP=1

就会将所有变量都打印出来

2、线程绑定:tls_init()

源码:

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
}

我们发现里面仅仅是做了线程key的绑定操作

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

我们知道这个函数用来调用析构函数的,那我们在这个地方打个断点,并且把自己写的析构函数注释掉,然后运行


image.png

发现有11个析构函数
我们在自己写一个析构函数,在运行

__attribute__((constructor))void func1(){
    printf("sdfsdf");
}
image.png

我们发现还是11个,这是为什么呢:

因为系统加载我们自己写额析构函数是在load方法执行之后,假如static_init要等load方法执行之后再执行就会导致_dyld_objc_notify_register()函数执行的条件不够充分,并且在static_init函数注释中也说明了,他需要在** _objc_init**之前执行

4、lock_init()

源码:


/***********************************************************************
* Lock management
**********************************************************************/
mutex_t runtimeLock;
mutex_t selLock;
mutex_t cacheUpdateLock;
recursive_mutex_t loadMethodLock;

void lock_init(void)
{
}

我们发现什么都没实现,可能是工厂方法,也可能是未开源,根据上面静态变量我们看书应该是是关于runtime、sel、cache、load加锁方面的管理

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

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

查看_objc_terminate函数,我们发现,当我们调用了未实现的方法时候就会崩溃到这个函数里面

image.png

并且根据注释我们知道是初始化libobjc's的异常回调系统,供map_images调用。

6、_dyld_objc_notify_register()

因为该函数是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());
        }
    }
}

我们发现在该函数里面dyld库将map_images、load_images、unmap_image都保存起来了
并且:
map_images是在notifyBatchPartial函数的这个地方调用了

image.png

load_images在registerObjCNotifiers、notifySingleFromCache、notifySingle里面调用了,

根据注释我们知道这个函数仅仅供OC的Runtime使用,加载所有类的 信息时候我们就要依赖这个注册函数的回调通知告诉dyld做了哪些事情,以及需要哪些环境,以及彼此之间的通讯,还就是当调用函数时候,系统执行的操作,以及当没有映射到的时候,系统应该如何操作,

下面我们来分析下里面的三个函数

二、镜像文件的加载:map_images

我们进到map_iamges源码看看

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源码

/***********************************************************************
* map_images_nolock
* Process the given images which are being mapped in by dyld.
* All class registration and fixups are performed (or deferred pending
* discovery of missing superclasses etc), and +load methods are called.
*
* info[] is in bottom-up order i.e. libobjc will be earlier in the 
* array than any library that links to libobjc.
*
* Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images.
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init();
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // Find all images with Objective-C metadata.
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();

#if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

#if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.

        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) {
            DisableInitializeForkSafety = true;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: disabling +initialize fork "
                             "safety enforcement because the app is "
                             "too old (SDK version " SDK_FORMAT ")",
                             FORMAT_SDK(dyld_get_program_sdk_version()));
            }
        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
#endif

    }

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}

发现源码好长啊,下面分享一个看源码的小技巧:

可用性的关键点一般都是if判断和while循环里面,还有就是通过返回值来确定函数的功能,找到return

根据上面原则,我们分析源码,发现上面if里面最终都是为了打印以及操作count,最后走到最下面


image.png

然后我们进到_read_images源码:

_read_images

源码太长就不放了,根据分析,我们发现在_read_images里面主要做了下面几个操作

(1)创建表

          - a、gdb_objc_realized_classes
          - b、allocatedClasses

(2)加载类信息

//加载类信息
    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_xpcobject、NSRunloop等系统类,以及自己创建的类,但此时类里面还没有信息
            Class cls = (Class)classlist[i];
            //通过readClassj函数q获取处理后的类。
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            //t初始化所有懒加载的类需要的内存空间 - 现在数据没有加载到的或者连类都没有初始化的
            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函数,我们进去看下

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;
    }
    
    // Note: Class __ARCLite__'s hack does not go through here. 
    // Class structure fixups that apply to it also need to be 
    // performed in non-lazy realization below.
    
    // These fields should be set to zero because of the 
    // binding of _objc_empty_vtable, but OS X 10.8's dyld 
    // does not bind shared cache absolute symbols as expected.
    // This (and the __ARCLite__ hack below) can be removed 
    // once the simulator drops 10.8 support.
#if TARGET_OS_SIMULATOR
    if (cls->cache._mask) cls->cache._mask = 0;
    if (cls->cache._occupied) cls->cache._occupied = 0;
    if (cls->ISA()->cache._mask) cls->ISA()->cache._mask = 0;
    if (cls->ISA()->cache._occupied) cls->ISA()->cache._occupied = 0;
#endif

    cls->fixupBackwardDeployingStableSwift();

    /*
     此处是为了测试自己创建的类是否会进入到下面的判断条件中
     */
//    const class_ro_t *lgro = (const class_ro_t *)cls->data();
//    const char *lgname     = lgro->name;
//    const char *lgnameTest = "LGPerson";
//    if (strcmp(lgname, lgnameTest) == 0) {
//        printf("是自己创建的LGPerson");
//    }

    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(getClassExceptSomeSwift(mangledName));
    } else {
        //此时类已经有地址了,所以将类插入到两张表里,这个时候内存中就有个类
        //将类插入到gdb_objc_realized_classes表中
        addNamedClass(cls, mangledName, replacing);
        //将类插入到allocatedClasses表中
        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;
}

经过测试我们发现是不会进入到 if (Class newCls = popFutureNamedClass(mangledName)) {这个条件判断里的,除掉这段代码,我们发现readClass只是将类插入到两张表中

这样我们发现readClass主要是将类加入到gdb_objc_realized_classes和allocatedClasses两张表中

(3)将所有类做重映射

    //将所有类做重映射,主要是修复重映射,一般走不进了
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            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]);
            }
        }
    }

经测试一般正常类因为noClassesRemapped()这个条件是不满足的,所以走不到里面去

(4)将所有SEL都注册到namedSelectorsz表中

      //将所有SEL都h注册到namedSelectorsz表中
    // 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);
            }
        }
    }

我们在看一下sel_registerNameNoLock方法里面的实现

SEL sel_registerNameNoLock(const char *name, bool copy) {
    return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
}
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;
    //将字符串转成SEL
    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) {
        //从内存中读取到方法名SEL
        result = sel_alloc(name, copy);
        // fixme choose a better container (hash not map for starters)
        //将方法插入到map表里
        NXMapInsert(namedSelectors, sel_getName(result), result);
    }

    return result;
}

我们发现在这一步,系统是拿到SEL,通过sel_cname()转成const char *,然后又通过sel_alloc()将const char *转成SEL存在map表里,我们在这个地方打个断点看看前后的const char *有什么区别

image.png

通过LLDB,我们发现最开始的const char *是一个指针加上方法名,然后转换后的const char *只是个方法名,最后映射到表里是将方法名和方法指针映射在一起的,这样验证了方法查找顺序是,通过方法名SEL拿到方法指针,然后再通过方法指针拿到方法的实现

(5)修复函数指针遗留

    //修复函数指针遗留
    // 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++) {
            fixupMessageRef(refs+i);
        }
    }

这个地方我们加上断点,发现for循环中count一直为0,说明这个message_ref_t跟我们的类一点关系都没有,并且我们现在的目的是想看到我们的数据被加载,说以这一步也跳过

(6)将所有protocols添加到Protocol_map表中

    //将所有protocols添加到Protocol_map表中
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

这一步跟上一步一样,也跟我们的类如何加载没关系,跳过

(7)对所有protocol做重映射

    //对所有protocol做重映射,实现非懒加载的类
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) {
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

也跟你上面一样,跳过

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

//初始化所有非懒加载类,进行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);

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

这里我们可以发现前半部分是初始化类的缓存方面操作,然后将类添加到表里,再通过realizeClassWithoutSwift对类做一下处理,下面我们看看 realizeClassWithoutSwift的源码

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

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
    //下面会通过递归处理cls的父类和元类,这个地方是递归终止点
    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?

    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内存空间
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        //将ro里的内容给rw的ro
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        //将创建的rw赋值给cls,此时rw里面的数据还是为空
        cls->setData(rw);
    }

    isMeta = ro->flags & RO_META;

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


    // 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的父类和元类做处理,确保父类已经都初始化完成,然后在下面将supercls和metacls赋值给cls的superclass和isa属性
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

#if SUPPORT_NONPOINTER_ISA
    // 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  &&  !(ro->flags & RO_META)  &&  
             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->setInstancesRequireRawIsa(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
    //将子类赋值给父类的firstSubclass熟悉,父类中也有个属性指向子类
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    //把ro数据写入到rw中
    methodizeClass(cls);

    return cls;
}

总结发现,在该方法里面创建了rw的内存空间,处理cls的同时也处理了cls的父类和元类,并且将父类和元类分别赋值给了cls的superclass和isa,同时也将子类赋值给父类,做双向链接,最后又做了methodizeClass,将ro的数据写入到rw中,我们看看如何将ro数据写入到rw中呢

/***********************************************************************
* 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();
    auto rw = cls->data();//读取到rw,此时rw还是为空
    auto ro = rw->ro;//读取到ro

    // 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.
    //将ro的方法写入到rw的methods中,我们会发现添加方法,协议,属性都是用attachLists函数
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }
    //将ro的属性写入到rw的properties中
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }
    //将ro的分类写入到rw的protocols中
    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*/);

    if (PrintConnecting) {
        if (cats) {
            for (uint32_t i = 0; i < cats->count; i++) {
                _objc_inform("CLASS: attached category %c%s(%s)", 
                             isMeta ? '+' : '-', 
                             cls->nameForLogging(), cats->list[i].cat->name);
            }
        }
    }
    
    if (cats) free(cats);

#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
}

我们发现,该方法是将ro中的methods、properties、protocols全部copy到rw中,并且我们发现method_list_t、property_list_t、protocol_list_t都是继承自

entsize_list_tt<property_t, property_list_t, 0>

并且都是通过attachLists将ro的数据copy到rw中。

为什么要将ro数据添加到rw中呢,因为ro属于类的本身数据,最基础的数据,在编译器就生成的,但是OC昨晚运行时语言,可以对类进行动态操作,这样就有了rw,通过分类和runtime给类添加的属性和和方法都会添加到rw中,同时rw中也有ro的所有数据

我们来看看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;
        //将老数据移动到新的容器中,因为是针对整个内存块的,这样比较安全,并且缓存空间遵循io原则,先进的在后面
            /*
             开始的位置:array()->lists + addedCount
             移动的内容:array()->lists
             移动的大小:oldCount * sizeof(array()->lists[0])
             */
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            //将新数据copy到容器中,因为已经确定了大小这样比较快
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            //如果没有list,那么且新添加的为空,那直接将addedLists[0]给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;
            //因为内存空间是新h创建的,可以直接将老数据放到指定位置
            if (oldList) array()->lists[addedCount] = oldList;
            //将新数据方法集合的最前面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

根据代码我们知道attachLists的操作:

1、如果类中已经有数据,那么就将内存扩容到能放下新老数据的大小,并且按顺序排列,先存的在后面,后存的在前面
2、如果类中没数据,且新加的数据只有一条,那么直接将新数据的指针给list
3、假如类中没数据,且新数据有多条,那么类就新开辟内存空间,将数据放进去

这里我们来看下malloc和realloc的区别:malloc和realloc

1、realloc是在已经分配好内存块的重新分配,如果开始指针分配为NULL,则和malloc用法一致,否则如果开始内存块小,保存原内存块,再次基础新增,如果是开始内存块大,则在此基础减去尾部内存块。返回值是分配好内存块的头指针
2、malloc(zise_t size);malloc是对没有分配过内存块的直接进行分配,返回值是分配好内存块的返回值是分配好内存块的头指针。通过malloc分配好的内存块一般要用free(size_t size)来释放内存块。

我们再看看memcpy和memmove的区别:memcpy和memmove:

1、memcpy()函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存区域不能重叠。
2、memmove() 函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存可以重叠。

针对上面操作,因为老数据跟扩容的数据是在同一片内存块中,不方便整段copy,这样会影响内存结构不够安全,所以采用的是move,新数据本来就不在一片内存,所以采用的是内存拷贝

初始化类总结:
  • 1、授信会对类的cache进行初始化,然后将类添加到表里
  • 2、然后开辟rw内存空间,并且将ro赋值给rw的ro属性,同时通过递归初始化话父类和元类,再将父类的指针和元类的指针赋给cls的superClass和isa
  • 3、再将ro里面的数据加载到rw的methods、properties、protocols里面,

(9)遍历已标记的懒加载的类,并做初始化操作

    // Realize newly-resolved future classes, in case CF manipulates them
    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);
    }    
image.png

从上面代码中,我们测试是不会走到这个条件判断中来了的,所以resolvedFutureClasses会一直为空,所以这部分基本不会走

(10)处理所有的category,包括CLass和Meta Class

(11)初始化所有未初始化的类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容