前文dyld加载流程中说到,dyld在初始化的时分会先初始化libsystem,然后是libdispatchlibobjc;在libobjc_objc_init中经过调用_dyld_objc_notify_register函数将map_images, load_images, unmap_image三个函数回调给dyld来调用。

本文聚焦于前两个函数,也便是加载类的流程。在看加载类的流程之前,需求先做一些预备作业,能够看下_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++的静态构造函数
    static_init();
    //初始化runtime运转环境 初始化两张表-分类的表的初始化-类的表的初始化
    runtime_init();
    //反常处理的初始化
    exception_init();
#if __OBJC2__
    //关于缓存的初始化
    cache_t::init();
#endif
    //MAC-OSX 处理初始化反常处理回调
    _imp_implementationWithBlock_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

能够看到_objc_init也首要是在做初始化的相关操作,包括环境变量、线程tls设置、runtime、cache等等,如下所示:

  • environ_init() 环境变量初始化;
  • tls_init() 创立线程的析构函数,处理线程key的绑定;
  • static_init() 运转c++的静态构造函数;
  • runtime_init() 初始化两张表-分类的表的初始化-类的表的初始化;
  • exception_init() 反常处理的初始化;
  • didCallDyldNotifyRegister 标识对_dyld_objc_notify_register的调⽤已完结。;

environ_init

image.png
environ_init是读取environment variables的一些配置信息,environment variablesEdit Scheme -> Run -> Argments -> Environment Variables 中配置。
上方源码中PrintOptions和PrintHelp参数能够经过在environment variables中设置OBJC_PRINT_OPTIONSOBJC_HELP来更改值:

  • OBJC_PRINT_OPTIONS是否输出OBJC已设置的选项
  • OBJC_HELP输出OBJC已设置的选项
    image.png
    下面咱们测验增加几个环境变量看下效果:

OBJC_DISABLE_NONPOINTER_ISA

OBJC_DISABLE_NONPOINTER_ISA为判别是否敞开指针优化。YES表明纯指针,NO便是运用nonpointer isa

  • 未设置OBJC_DISABLE_NONPOINTER_ISA,目标的isa地址的二进制打印,结尾为1,表明isa不只是类目标地址,isa中包括了类信息、目标的引证计数
  • 设置OBJC_DISABLE_NONPOINTER_ISA环境变量为YES后,结尾变成了0,此时isa便是类的首地址,是一个纯的isa
    如下图:
    image.png
    image.png

OBJC_PRINT_LOAD_METHODS

OBJC_PRINT_LOAD_METHODS:打印 Class 及 Category 的 + (void)load 办法的调用信息,包括动态库中的也会被打印。所以,OBJC_PRINT_LOAD_METHODS能够监控一切的+load办法(包括动态库)
当咱们在类中增加load办法后,运转程序,一切调用load函数的就会打印出来,如下图:
image.png

其他环境变量设置:

环境变量说明.png

map_images

回到_dyld_objc_notify_register中,咱们先看下第一个参数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直接调用了map_images_nolockmap_images_nolock内容非常多,咱们分段来看下:
首要是传入参数:

void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
  • mhCount:mach-o header count,即mach-o header 个数
  • mhPaths:mach-o header Paths,即header的途径数组
  • mhdrs:单个mhdr指的是mach-o header 的 header 指针
// 局部静态变量,表明第一次调用
    static bool firstTime = YES;
    // hList 是统计 mhdrs 中的每个 mach_header 对应的 header_info
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;
    //如有必要,履行初次初始化。
    // Perform first-time initialization if necessary.
    //此函数在 ordinary library 初始化程序之前调用。
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    // 假如是第一次加载,则预备初始化环境
    if (firstTime) {
        preopt_init();
    }
    // 敞开 OBJC_PRINT_IMAGES 环境变量时,启动时则打印 images 数量。
       // 如:objc[10503]: IMAGES: processing 296 newly-mapped images...
    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }

这一段最首要的便是preopt_init,假如是第一次加载,则预备初始化环境,那么初始化什么环境呢?咱们暂且不说,咱们先持续往下看下段代码:

// Find all images with Objective-C metadata.
    hCount = 0;
    // 核算 class 的数量。依据总数调整各种表格的巨细。
    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            // 获得指定 image 的 header 指针
            const headerType *mhdr = (const headerType *)mhdrs[i];
            // 以 mdr 构建其 header_info,并增加到大局的 header 列表中(是一个链表,大约看源码到现在仍是第一次看到链表的运用)。
            // 且经过 GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist"); 读取 __objc_classlist 区中的 class 数量增加到 totalClasses 中,
             // 以及未从 dyld shared cache 中找到 mhdr 的 header_info 时,增加 class 的数量到 unoptimizedTotalClasses 中。
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            // 这儿有两种情况下 hi 为空:
            // 1. mhdr 的 magic 不是既定的 MH_MAGIC、MH_MAGIC_64、MH_CIGAM、MH_CIGAM_64 中的任何一个
            // 2. 从 dyld shared cache 中找到了 mhdr 的 header_info,而且 isLoaded 为 true()
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            // #define MH_EXECUTE 0x2 /* demand paged executable file demand 分页可履行文件 */
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__   // 依据首要可履行文件的巨细调整一些数据结构的巨细
                // If dyld3 optimized the main executable, then there shouldn't
                // be any selrefs needed in the dynamic map so we can just init
                // to a 0 sized map
                if ( !hi->hasPreoptimizedSelectors() ) {
                  size_t count;
                    // 获取 __objc_selrefs 区中的 SEL 的数量
                  _getObjc2SelectorRefs(hi, &count);
                  selrefCount += count;
                    // GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs");
                    // struct message_ref_t {
                    //     IMP imp;
                    //     SEL sel;
                    // };
                    // 获取 __objc_msgrefs 区中的 message 数量
                  _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)":"");
            }
        }
    }

这又是个大段篇幅,咱们逐渐分析:
(const headerType *)mhdrs[i] 获得指定 image 的 header 指针,这儿也是将mach_header转换为headerType指针,然后便是addHeader办法。

addHeader

static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
    header_info *hi;
    if (bad_magic(mhdr)) return NULL;
    bool inSharedCache = false;
    // Look for hinfo from the dyld shared cache.
    hi = preoptimizedHinfoForHeader(mhdr);
    if (hi) {
        // Found an hinfo in the dyld shared cache.
        // Weed out duplicates.
        if (hi->isLoaded()) {
            return NULL;
        }
        inSharedCache = true;
        // Initialize fields not set by the shared cache
        // hi->next is set by appendHeader
        hi->setLoaded(true);
        //部分代码省掉
    }
    else 
    {
        // Didn't find an hinfo in the dyld shared cache.
        // Locate the __OBJC segment
        size_t info_size = 0;
        unsigned long seg_size;
        const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
        const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
        if (!objc_segment  &&  !image_info) return NULL;
        // Allocate a header_info entry.
        // Note we also allocate space for a single header_info_rw in the
        // rw_data[] inside header_info.
        hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
        // Set up the new header_info entry.
        hi->setmhdr(mhdr);
#if !__OBJC2__
        // mhdr must already be set
        hi->mod_count = 0;
        hi->mod_ptr = _getObjcModules(hi, &hi->mod_count);
#endif
        // Install a placeholder image_info if absent to simplify code elsewhere
        static const objc_image_info emptyInfo = {0, 0};
        hi->setinfo(image_info ?: &emptyInfo);
        hi->setLoaded(true);
        hi->setAllClassesRealized(NO);
    }
#if __OBJC2__
    {
        size_t count = 0;
        if (_getObjc2ClassList(hi, &count)) {
            totalClasses += (int)count;
            if (!inSharedCache) unoptimizedTotalClasses += count;
        }
    }
#endif
    appendHeader(hi);
    return hi;
}

这儿addHeader函数大体上分为一下几步

  • 判别一下当时的headerdyld的同享缓存中有没有
  • 假如有的话直接设置已加载
  • 假如同享缓存中没有,那么就实行“封装操作
  • 封装成功今后,加入到链表中。
    其间preoptimizedHinfoForHeader(mhdr)为判别当时的headerdyld的同享缓存中有没有:
header_info *preoptimizedHinfoForHeader(const headerType *mhdr)
{
#if !__OBJC2__
    // fixme old ABI shared cache doesn't prepare these properly
    return nil;
#endif
    objc_headeropt_ro_t *hinfos = opt ? opt->headeropt_ro() : nil;
    if (hinfos) return hinfos->get(mhdr);
    else return nil;
}

objc_opt_t

查看源码能够看到是经过opt来获取数据,也便是说同享缓存的数据都存放在opt内,经过源码可进一步查看opt类型,其为静态变量~0便是0Xffffffff,这是块安全区域

static const objc_opt_t *opt = (objc_opt_t *)~0;

objc_opt_t结构体内部存储的数据大体上有一下几种

  • 类的缓存clsopt()
  • 协议的缓存protocolopt()
  • 头的缓存headeropt_rw()
  • 选择器的缓存selopt()
struct alignas(alignof(void*)) objc_opt_t {
    const objc_selopt_t* selopt() const {
        if (selopt_offset == 0) return NULL;
        return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
    }
    objc_selopt_t* selopt() { 
        if (selopt_offset == 0) return NULL;
        return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
    }
    struct objc_headeropt_ro_t* headeropt_ro() const {
        if (headeropt_ro_offset == 0) return NULL;
        return (struct objc_headeropt_ro_t *)((uint8_t *)this + headeropt_ro_offset);
    }
    struct objc_clsopt_t* clsopt() const { 
        if (clsopt_offset == 0) return NULL;
        return (objc_clsopt_t *)((uint8_t *)this + clsopt_offset);
    }
    struct objc_protocolopt_t* protocolopt() const {
        if (unused_protocolopt_offset == 0) return NULL;
        return (objc_protocolopt_t *)((uint8_t *)this + unused_protocolopt_offset);
    }
    struct objc_headeropt_rw_t* headeropt_rw() const {
        if (headeropt_rw_offset == 0) return NULL;
        return (struct objc_headeropt_rw_t *)((uint8_t *)this + headeropt_rw_offset);
    }
};

回到addHeader函数中,假如hi有值,那么设置已加载,假如没有值,则进行封装,然后便是往链表中增加数据:

  • FirstHeader为链表的头节点
  • LastHeader为链表的尾节点
void appendHeader(header_info *hi)
{
    // Add the header to the header list. 
    // The header is appended to the list, to preserve the bottom-up order.
    hi->setNext(NULL);
    if (!FirstHeader) {
        // list is empty
        FirstHeader = LastHeader = hi;
    } else {
        if (!LastHeader) {
            // list is not empty, but LastHeader is invalid - recompute it
            LastHeader = FirstHeader;
            while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
        }
        // LastHeader is now valid
        LastHeader->setNext(hi);
        LastHeader = hi;
    }
#if __OBJC2__
    if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) {
        foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) {
            uintptr_t start = (uintptr_t)seg->vmaddr + slide;
            objc::dataSegmentsRanges.add(start, start + seg->vmsize);
        });
    }
#endif
}

header_info

static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)

最终咱们看到该函数回来的是一个header_info的数据,header_info既是个结构体也是个链表(代码过长不粘了),经过getNext来获取链表的下一节点数据,setNext(header_info *v)设置节点数据,头节点为FirstHeader

preopt_init

到这儿咱们就知道第一步的preopt_init是做什么的了,也便是初始化同享缓存的数据,包括类、协议、头部信息、选择器等

接下来便是依据获取images中的 SEL 的数量,然后进入如下代码:

if (firstTime) {
        // 初始化 selector 表并注册内部运用的 selectors。
        sel_init(selrefCount);
        //这儿的 arr_init 函数超重要,可看到它内部做了三件事:
        // 1. 主动开释池的初始化(实践是在 TLS 中以 AUTORELEASE_POOL_KEY 为 KEY 写入 tls_dealloc 函数(主动开释池的毁掉函数:内部一切 pages pop 并 free))
        // 2. SideTablesMap 初始化,也可理解为 SideTables 的初始化(为 SideTables 这个静态大局变量拓荒空间)
        // 3. AssociationsManager 的初始化,即为大局运用的相关目标表拓荒空间
        // void arr_init(void)
        // {
        //     AutoreleasePoolPage::init();
        //     SideTablesMap.init();
        //     _objc_associations_init();
        // }
        arr_init();
	// 部分代码省掉
        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
    }
//下面就来到了最中心的当地
    // 以 header_info *hList[mhCount] 数组中收集到的 images 的 header_info 为参,直接进行 image 的读取
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    // 把开始时初始化的静态局部变量 firstTime 置为 NO
    firstTime = NO;
    // 一切设置完毕后调用 image 加载函数。
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }

sel_init

sel_init首要是初始化selector表,该表定义如下:

static objc::ExplicitInitDenseSet<const char *> namedSelectors;

namedSelectors是个大局变量,存储一切的办法名SEL,内部结构是哈希表DenseMap

arr_init

arr_init首要做以下作业:

    1. 主动开释池的初始化,关于AutoreleasePool的内容能够看我的另一篇文章:Autoreleasepool
    1. SideTablesMap 初始化,也可理解为 SideTables 的初始化(为 SideTables 这个静态大局变量拓荒空间)
    1. AssociationsManager 的初始化,即为大局运用的相关目标表拓荒空间

_read_images

之后就进入到了_read_images,先看下传入的参数。

_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
  • hList 统计 mhdrs 中的每个 mach_header 对应的 header_info
  • hCount统计到的header_info数量
  • totalClasses 核算到的一切class 的数量,该数量包括unoptimizedTotalClasses数量,可在addHeader函数中看到对这两个数的赋值(地址饮用)。
  • unoptimizedTotalClasses 不在同享缓存区找到的类的数量。
    然后进入到函数体里面:

加载类到gdb_objc_realized_classes表中

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;
    // 静态局部变量,假如是第一次调用 _read_images 则 doneOnce 值为 NO
    static bool doneOnce;
    bool launchTime = NO;
    // 测量 image 加载进程的持续时间
    // 对应 objc-env.h 中的 OPTION( PrintImageTimes, OBJC_PRINT_IMAGE_TIMES, "measure duration of image loading steps")
    TimeLogger ts(PrintImageTimes);
    runtimeLock.assertLocked();
    //EACH_HEADER 是给下面的 for 循环运用的宏,遍历 hList 数组中的 header_info
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // 第一次调用 _read_images 时,doneOnce 值为 NO,会进入 if 履行里面的代码
    if (!doneOnce) {
        doneOnce = YES;
        launchTime = YES;
       // 部分代码省掉
        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        // isPreoptimized 假如咱们有一个有效的优化同享缓存(valid optimized shared cache),则回来 YES。 
        // 然后是不论三目运算符回来的是 unoptimizedTotalClasses 仍是 totalClasses,它都会和后面的 4 / 3 相乘, 
        // 注意是 4 / 3
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
       // gdb_objc_realized_classes 是一张大局的哈希表
       // 实践上它存放的是不在 dyld shared cache 中的 class,无论该 class 是否 realized。
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        ts.log("IMAGE TIMES: first time tasks");
       }

到这儿根本是一些环境变量的初始化,以及在为
读取header_info内容做预备。

  • doneOnce保证在调用 _read_images 的时分只履行一次
  • NXCreateMapTable为一个哈希表,经过类名来存储类目标(以及读取类目标)
  • gdb_objc_realized_classes 是一个大局的类表,只要 class 没有在同享缓存中,那么不论其完成或许未完成都会存在这个类表里面。

将一切的SEL都注册到namedSelectors表中,而且修正函数指针

    // Fix up @selector references
    // Note this has to be before anyone uses a method list, as relative method
    // lists point to selRefs, and assume they are already fixed up (uniqued).
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        // 遍历 header_info **hList 中的 header_info
        for (EACH_HEADER) {
            // 假如指定的 hi 不需求预优化则跳过
            if (hi->hasPreoptimizedSelectors()) continue;
            // 依据 mhdr()->filetype 判别 image 是否是 MH_BUNDLE 类型
            bool isBundle = hi->isBundle();
            // GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs");
            // 获取 __objc_selrefs 区中的 SEL
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            // 记录数量
            UnfixedSelectors += count;
            // static objc::ExplicitInitDenseSet<const char *> namedSelectors;
            // 是一个静态大局 set,用来存放 Selector(姓名,Selector 自身便是字符串)
            // 遍历把 sels 中的一切 selector 放进大局的 selector 集合中
            for (i = 0; i < count; i++) {
                // sel_cname 函数内部完成是回来:(const char *)(void *)sel; 即把 SEL 强转为 char 类型
                const char *name = sel_cname(sels[i]);
                // 注册 SEL,并回来其地址
                SEL sel = sel_registerNameNoLock(name, isBundle);
                // 假如 SEL 地址发生变化,则把它设置为相同
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
  • _getObjc2SelectorRefs该函数是拿到Mach-o中的静态段__obj_selrefs
  • 后文中一切经过_getObjc2开头的Mach-o静态段获取,都对应不同的section name,如下代码:
//      function name                 content type     section name
GETSECT(_getObjc2SelectorRefs,        SEL,             "__objc_selrefs"); 
GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
GETSECT(_getObjc2ClassList,           classref_t const,      "__objc_classlist");
GETSECT(_getObjc2StubList,            stub_class_t *const,   "__objc_stublist");
GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");
GETSECT(_getObjc2CategoryList,        category_t * const,    "__objc_catlist");
GETSECT(_getObjc2CategoryList2,       category_t * const,    "__objc_catlist2");
GETSECT(_getObjc2NonlazyCategoryList, category_t * const,    "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");

这儿的这段代码首要做了一件事情,也便是将一切的SEL都注册到namedSelectors表中,且当 SEL *sels = _getObjc2SelectorRefs(hi, &count); 中的 SEL 和经过 SEL sel = sel_registerNameNoLock(name, isBundle); 注册回来的 SEL 不同时,就把 sels 中的 SEL 修正为 sel_registerNameNoLock 中回来的地址,如下图所示。
image.png
这一进程也是前一篇文章中提到的rebase的进程,也便是修正指向当时镜像内部的资源指针,导致这两个地址不同的原由于alsr偏移

Discover classes

    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    // 发现 classes。修正 unresolved future classes。符号 bundle classes。
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            // Image 已充沛优化,咱们无需调用 readClass()
            continue;
        }
        // GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist");
        // 获取 __objc_classlist 区中的 classref_t
        // 从编译后的类列表中取出一切类,获取到的是一个 classref_t 类型的指针
        // classref_t is unremapped class_t* ➡️ classref_t 是未重映射的 class_t 指针
        // typedef struct classref * classref_t; // classref_t 是 classref 结构体指针
        classref_t const *classlist = _getObjc2ClassList(hi, &count);
        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i]; 人大在;s d
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                // realloc 原型是 extern void *realloc(void *mem_address, unsigned int newsize);
                // 先判别当时的指针是否有足够的接连空间,假如有,扩展 mem_address 指向的地址,而且将 mem_address 回来,
                // 假如空间不够,先依照 newsize 指定的巨细分配空间,将原有数据从头到尾拷贝到新分配的内存区域,
                // 然后开释原来 mem_address 所指内存区域(注意:原来指针是主动开释,不需求运用 free),
                // 同时回来新分配的内存区域的首地址,即从头分配存储器块的地址。
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

这一部分首要做了一件事:发现并读取class

readClass

readClass首要是读取类,在未调用该办法前,cls只是一个地址,该办法履行后,cls存入表中,是一个类的名称

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
 读取由编译器编写的类和元类。
* Returns the new class pointer. This could be:
 回来新的类指针。这可能是:
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
 请注意,此函数履行的一切作业都由 mustReadClasses() 预检。
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 类的姓名
    const char *mangledName = cls->nonlazyMangledName();
    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->setSuperclass(nil);
        return nil;
    }
    // 假如 cls 是 swift 类,进行一些修正
    cls->fixupBackwardDeployingStableSwift();
    //判别class它是否存在于 future_named_class_map 中
    Class replacing = nil;
    if (mangledName != nullptr) {
        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));
            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());
            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);
            addRemappedClass(cls, newCls);
            replacing = cls;
            cls = newCls;
        }
    }
    // headerIsPreoptimized 是外部参数,只要该类禁用了预优化才会回来 true,所以到这儿会走下面的 else
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    // 假如 headerIsBundle 为真,则设置下面的标识位 RO_FROM_BUNDLE
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    return cls;
}

这儿边有一些判别,咱们不知道咱们增加的类会走哪些分支,咱们能够增加如下代码然后加断点调试来看下到底会走哪些分支:

    //FMUserInfo为我自己增加的类名
    const char * userinfoName = "FMUserInfo";
    auto fm_ro = (const class_ro_t *)cls ->data();
    //判别是否是元类
    auto fm_isMeta = fm_ro->flags & RO_META;
    if (strcmp(mangledName,userinfoName) == 0 && !fm_isMeta) {
        <#statements#>
    }

其实整个readClass的进程,最终实践运转函数首要为以下两个,如下:

  • addNamedClass 该函数是把name和cls增加到命名为gdb_objc_realized_classes的表中去,在addNamedClass函数中,调用NXMapInsertcls 和 name 刺进到 NXMapTable 中。
  • addClassTableEntry类和元类加入到allocatedClasses表中
NXMapInsert
typedef struct _NXMapTable {
    /* private data structure; may change */
    const struct _NXMapTablePrototype	* _Nonnull prototype;
    unsigned	count;
    unsigned	nbBucketsMinusOne;
    void	* _Nullable buckets;
} NXMapTable
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    MapPair	*pairs = (MapPair *)table->buckets;
    unsigned	index = bucketOf(table, key);
    MapPair	*pair = pairs + index;
    if (key == NX_MAPNOTAKEY) {
	_objc_inform("*** NXMapInsert: invalid key: -1\n");
	return NULL;
    }
    unsigned numBuckets = table->nbBucketsMinusOne + 1;
    if (pair->key == NX_MAPNOTAKEY) {
	pair->key = key; pair->value = value;
	table->count++;
	if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
	return NULL;
    }
    if (isEqual(table, pair->key, key)) {
	const void	*old = pair->value;
	if (old != value) pair->value = value;/* avoid writing unless needed! */
	return (void *)old;
    } else if (table->count == numBuckets) {
	/* no room: rehash and retry */
	_NXMapRehash(table);
	return NXMapInsert(table, key, value);
    } else {
	unsigned	index2 = index;
	while ((index2 = nextIndex(table, index2)) != index) {
	    pair = pairs + index2;
	    if (pair->key == NX_MAPNOTAKEY) {
		pair->key = key; pair->value = value;
		table->count++;
		if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
		return NULL;
	    }
	    if (isEqual(table, pair->key, key)) {
		const void	*old = pair->value;
		if (old != value) pair->value = value;/* avoid writing unless needed! */
		return (void *)old;
	    }
	}
	/* no room: can't happen! */
	_objc_inform("**** NXMapInsert: bug\n");
	return NULL;
    }
}

整个NXMapInsert的操作便是一个普通的哈希表的刺进操作

  • bucketOf(table, key)调用 table->prototype->hash 函数核算 key 在 table 中的哈希值
  • numBuckets值为table->nbBucketsMinusOne + 1,也便是buckets 长度桶长度。
  • 假如pair->key的值为-1,表明该方位还没有存入东西,则把 keyvalue 存在这儿,假如当时 table 存储的数据现已超过了其容量的 3 / 4,则进行扩容并从头哈希化里面的数据。
  • if (isEqual(table, pair->key, key))假如 pairkey 和入参 key 相同,则表明 key 现已存在于 table 中(则更新value
  • if (table->count == numBuckets)假如刚好没有空间了,则扩容并从头哈希化旧数据后,再测验刺进key和value
  • 假如发生了哈希碰撞,则采用开放寻址法来处理哈希冲突。
addClassTableEntry

addClassTableEntry 函数,将 cls 增加到大局的类表中。假如 addMeta 为 true,则也会把 cls 的元类增加到大局的类表中。

/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
 将 cls 增加到大局的类表中。假如 addMeta 参数为 true,则也会把 cls 的元类增加到大局的类表中。
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();
    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    // 在runtime_init 函数中,调用了 objc::allocatedClasses.init();对类的表进行了初始化。
    //这儿便是往类表中增加
    auto &set = objc::allocatedClasses.get();
    ASSERT(set.find(cls) == set.end());
    // isKnownClass 函数,假如 runtime 知道该类,则回来 true,当以下情况时回来 true:
    // 1. cls 坐落 shared cache
    // 2. cls 在加载 image 的 data segment 内
    // 3. cls 已用 obj_allocateClassPair 分配
    // 此操作的成果会缓存在类的 cls->data()->witness 中,
    // 即咱们的 class_rw_t 结构体的 witness 成员变量。
    // struct class_rw_t {
    //   ...
    //   uint16_t witness;
    //   ...
    // }
    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        // 把 cls 增加到类表中
        addClassTableEntry(cls->ISA(), false);
}

总结:readClass的首要操作便是将Mach-o中的类读取到大局表中,但目前类中的信息仅有两个:地址+名称,类中的data数据,需求经过下文的realizeClassWithoutSwift来增加。

回到read_image函数中,接下来便是修正重映射classes

修正重映射一些没有被镜像文件加载进来的类

    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class list 和 nonlazy class list 仍未映射。
    // Class refs and super refs are remapped for message dispatching.
    // Class refs 和 super refs 被从头映射为音讯调度。
    // 首要是修正重映射 classes,!noClassesRemapped() 在这儿为 false,所以一般走不进来,
    // 将未映射 class 和 super class 重映射,被 remap 的类都对错懒加载的类
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            // 遍历 classrefs 中的类引证,假如类引证已被从头分配或许是被忽略的弱链接类,
            // 就将该类引证从头赋值为从重映射类表中取出新类
            for (i = 0; i < count; i++) {
                // 修正 class ref,以防所引证的类已 reallocated 或 is an ignored weak-linked class。
                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]);
            }
        }
    }

这儿首要是将未映射的classsuper class进行重映射,其间:

  • _getObjc2ClassRefs是获取Mach-o中的静态段__objc_classrefs,即类的引证
  • _getObjc2SuperRefs是获取Mach-o中的静态段__objc_superrefs,即父类的引证

修正一些音讯

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

修正一些音讯的调用,这儿边最首要的便是fixupMessageRef
image.png

当类里面有协议时,协议的处理

    // 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->hasPreoptimizedProtocols();
        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        if (launchTime && isPreoptimized) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }
        bool isBundle = hi->isBundle();
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
  • 这儿经过NXMapTable *protocol_map = protocols();来创立protocol哈希表,表的名称为protocol_map
  • 经过_getObjc2ProtocolList获取到Mach-o中的静态段__objc_protolist协议列表,即从编译器中读取并初始化protocol
  • 循环遍历协议列表,经过readProtocol办法将协议增加到protocol_map哈希表中

修正没有被加载到的协议

// Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) {
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
        if (launchTime && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

这儿首要是经过_getObjc2ProtocolRefs获取到Mach-o的静态段__objc_protorefs。(
这儿的__objc_protorefs与上文中的__objc_protolist并不是同一个东西),然后遍历需求修正的协议,经过remapProtocolRef比较当时协议协议列表中的同一个内存地址的协议是否是相同的,假如不相同则替换

/***********************************************************************
* remapProtocolRef
* Fix up a protocol ref, in case the protocol referenced has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
    runtimeLock.assertLocked();
//获取协议列表中统一内存地址的协议
    protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
    //假如当时协议与同一内存地址协议不同则替换
    if (*protoref != newproto) {
        *protoref = newproto;
        UnfixedProtocolReferences++;
    }
}

分类处理

下一篇中说这部分,待续。

非懒加载类的加载

懒加载类非懒加载类的差异便是是否完成了+load办法

  • 完成了+load办法,便是非懒加载类
  • 没完成,便是懒加载类
    这是由于load会提前加载,load办法会在load_images中调用。
 // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            addClassTableEntry(cls);
            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

这儿首要是完成非懒加载类的加载处理:

  • hi->nlclslist(&count)中经过_getObjc2NonlazyClassList获取Mach-o的静态段__objc_nlclslist非懒加载类表;
  • 经过addClassTableEntry将非懒加载类刺进类表,存储到内存,假如现已增加就不会再增加,需求确保整个结构都被增加;
  • 经过realizeClassWithoutSwift完成当时的类,由于之前readClass读取到内存的只是只要地址+名称类的data数据并没有加载出来。
    详细的类如何完成,realizeClassWithoutSwift函数的调用,也放在下一篇中,待续。

没有被处理的类

// 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, nil);
            // 将此类及其一切子类符号为需求原始 isa 指针
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

read_images 总结

  • 1: 加载一切类到类的gdb_objc_realized_classes表中。
  • 2: 对一切类做重映射。
  • 3: 将一切SEL都注册到namedSelectors表中。
  • 4: 修正函数指针遗留。
  • 5: 将一切Protocol都增加到protocol_map表中。
  • 6: 对一切Protocol做重映射。
  • 7: 初始化一切⾮懒加载的类,进⾏rw、ro等操作。
  • 8:遍历已符号的懒加载的类,并做初始化操作。
  • 9:处理一切Category,包括Class和Meta Class
  • 10:初始化一切未初始化的类。

load_images

首要看下load_images源码:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        //加载一切分类
        loadAllCategories();
    }
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);
    // 找到load办法
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    //回调load办法
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

经过源码来看load_images函数全体上做了三件事:

  • loadAllCategories 加载一切分类
  • prepare_load_methods 找到load办法
  • call_load_methods 调用load办法

prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;
    runtimeLock.assertLocked();
    // GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
    // 获取一切 __objc_nlclslist 区的数据(一切非懒加载类)
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    // 遍历这些非懒加载类,并将其 +load 函数增加到 loadable_classes 数组中,优先增加其父类的 +load 办法,
       // 用于下面 call_load_methods 函数调用
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    // GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
       // 获取一切 __objc_nlcatlist 区的数据(一切非懒加载分类)
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    // 遍历这些分类
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        // 假如没有找到分类所属的类就跳出当时循环,处理数组中的下一个分类
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        // 假如分类所属的类没有完成就先去完成
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        // 需求调用 +load 的 categories 列表
        // static struct loadable_category *loadable_categories = nil;
        // 遍历这些分类,并将其 +load 办法增加到 loadable_categories 数组中保存
        add_category_to_loadable_list(cat);
    }
}
  • 这儿的schedule_class_load会进行一个递归调用,在找load办法的时分,会优先去找父类的load办法,然后把找到的办法增加到loadable_classes表里面去
  • add_class_to_loadable_list会去找分类的load办法,在找到之后相同增加到loadable_classes表里面去
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    // 假如 cls 不存在则 return(下面有一个针对 superclass 的递归调用)
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize
    if (cls->data()->flags & RW_LOADED) return;
    // Ensure superclass-first ordering
    // 这是一个递归调用
    // 优先处理 superclass 的 +load 函数
    schedule_class_load(cls->getSuperclass());
    // 将 cls 的 +load 函数增加到大局的 loadable_class 数组 loadable_classes 中,
       // loadable_class 结构体是用来保存类的 +load 函数的一个数据结构,其间 cls 是该类,method 则是 +load 函数的 IMP,
       // 这儿也能看出 +load 函数是不走 OC 的音讯转发机制的,它是直接经过 +load 函数的地址调用的!
    add_class_to_loadable_list(cls);
    // 将 RW_LOADED 设置到类的 Flags 中
    cls->setInfo(RW_LOADED); 
}

call_load_methods

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;
    loadMethodLock.assertLocked();
    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;
    // 创立主动开释池
    void *pool = objc_autoreleasePoolPush();
    do {
        // 调用类中的 +load 函数
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // 调用 loadable_classes 中的的类的 +load 函数,而且把 loadable_classes_used 置为 0
            call_class_loads();
        }
        // 2. Call category +loads ONCE
        // 调用 分类中的 +load 函数, 只调用一次 call_category_loads,由于上面的 call_class_loads 函数内部,
        // 现已把 loadable_classes_used 置为 0,所以除非有新的分类需求 +load,即 call_category_loads 回来 true,
        // 否则循环就结束了。
        more_categories = call_category_loads();
        // 假如 loadable_classes_used 大于 0,或许有更多分类需求调用 +load,则循环持续。(一般 loadable_classes_used 到这儿根本便是 0 了)
        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);
    // 主动开释池进行 pop
    objc_autoreleasePoolPop(pool);
    loading = NO;
}

load办法总结

  • 当父类和子类都完成load函数时,父类的load办法履行次序要优先于子类
  • 当一个类未完成load办法时,不会调用父类load办法
  • 类中的load办法履行次序要优先于分类(Category)
  • load办法运用了锁,所以是线程安全的。
  • 当有多个类别(Category)都完成了load办法,这几个load办法都会履行,但履行次序不确定(其履行 次序与类别在Compile Sources中呈现的次序一致)
  • 当然当有多个不同的类的时分,每个类load 履行次序与其在Compile Sources呈现的次序一致