这里主要是解释:

  1. iOS下RunTime分析一(OC文件编译为C++)中的第4点疑问。
  2. iOS下RunTime分析三(扩展C++源码)中的总结的疑问。

该runtime源码分析及runtime源码都是基于MacOS 10.12.4 版本。

我们一般写程序都是以main函数作为程序入口开始编程的。那么在程序启动到开始执行main函数之间代码是如何调用的,每个类的load方法又是在什么情况下调用的?某个类的category中的方法、属性又是如何与主类联系起来的?一起来分析下。

首先需要大家把runtime的源码先下载下来,还可以编译runtime源码生成自己的libobjc.A.dylib。可以参考该文章:objc - 编译Runtime源码objc4-680

Objective-C的运行是依赖于runtime库,runtime与其他库一样,都是通过dyld动态加载进来的。

runtime运行OC,开始加载OC类,入口如下: 在该函数中主要加载OC类的主要是_dyld_objc_notify_register(&map_images, load_images, unmap_image);这个函数。

map_images

可以看下该方法的实现:

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

这里的paths里面的其实是dyld加载的各个OC的类库,mdhrs是与类库相对应的一个mach_header类型的变量,count就是paths的数组长度。可以看下paths里面的内容:

/Users/zyh/Library/Developer/Xcode/DerivedData/objc-amdcxyrwpmnquyhanfsebqgcarms/Build/Products/Debug/debug-objc
/System/Library/PrivateFrameworks/RemoteViewServices.framework/Versions/A/RemoteViewServices
/System/Library/PrivateFrameworks/Backup.framework/Versions/A/Backup
/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/Ink.framework/Versions/A/Ink
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Debugger/libViewDebuggerSupport.dylib
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
...

map_images中可以看到主要实现的逻辑在map_images_nolock中,该函数的实现代码主要就是两个,现在用伪代码实现下并加注释(请详细看注释,是精华所在)说明:

/*
 mhCount:当前dyld加载的OC的类库的数量
 mhPaths:OC类库的文件地址
 mhdirs:每个OC库文件的文件信息
 */
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    header_info *hList[mhCount];
    uint32_t hCount = 0;
    
    int totalClasses = 0;//所有的mhPaths的库中总共包含了多少个OC类。
    int unoptimizedTotalClasses = 0;//这个目前与totalClass一样。
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];
            /*
             在这里totalClasses、unoptimizedTotalClasses不是值传参,是引用传参。
             该函数的作用是:
             1. 根据mhdr信息,生成一个header_info的一个变量info,该变量保存了mhdr这个值,并将info放到以FirstHeader为头部的链表中。
             2. 根据mhdr信息计算mhPath[i]这个地址的类库里面有多少个OC类,并加到totalClasses中。
             */
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                continue;
            }
            
            hList[hCount++] = hi;
        }
    }
    
    if (hCount > 0) {
        //真正的加载类、协议、category的地方。单独拿出来分析。
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}

下面来看下_read_images的伪代码实现:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    size_t count;
    size_t i;
    static bool doneOnce;
    
    if (!doneOnce) {
        doneOnce = YES;
        //这里会根据编译器是否需要优化而对类的数量做了改变。
        int namedClassesSize =
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        
        //该变量是个全局的NXMapTable,是个key-value的hashTable。存储了加载出来的类对象。
        gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    }
    
    //加载类对象
    //这里加载的类对象只是将类对方存到以gdb_objc_realized_classes为变量的一个map中
    //key值就是该对象的名字
    //除此之外没有做任何操作,比如与isA的关联和superclass的关联等等。
    for (EACH_HEADER) {
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }
        
        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();
        //其实就是从程序的数据段寻找__objc_classlist类型的变量。可以参考clang编译的C++源码。
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            //将类加载出来放到gdb_objc_realized_classes中。
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            
            if (newCls != cls  &&  newCls) {
                resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses,
                        (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
    
    //加载类中实现的协议对象
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        //该变量是一个全局的NXMapTable,存储着各个OC类库中的协议的结构体静态变量。key-value
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();
        
        //其实就是从程序的数据段寻找__objc_protolist类型的变量。可以参考clang编译的C++源码。
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            //该方法主要就是将protolist[i]存入protocol_map中。
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);
        }
    }
    
    //实现类对象:将类对象与方法函数关联、指定类对象的isA和superclass等等。
    //该处并不是实现了所有的类对象,只有实现了+(void)load方法的类对象,才会执行这里的逻辑。
    //那么没有实现load方法的类对象只是放到了上面的map中,在真正使用时才会去关联方法、指定isA等操作。
    for (EACH_HEADER) {
        //从程序的数据段寻找__objc_nlclslist类型的变量。
        classref_t *classlist =
        _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            //这里主要是做了两步骤:
            //1. 设置了cls的superclass以及data(),并将cls添加到superclass的链表中。
            //2. 添加methodlist方法、propertys方法、protocol方法以及category方法。
            //这里添加category方法时是从unattachedCategories()中的hashtable中取的,
            //这时如果category方法还没有加到这个table中,那么返回就是空的。
            realizeClass(cls);
        }
    }
    
    //添加category方法。
    for (EACH_HEADER) {
        //从程序的数据段寻找__objc_catlist类型的变量。
        category_t **catlist =
        _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            
            if (!cls) {
                continue;
            }
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols
                ||  cat->instanceProperties)
            {
                //1. 首相将cat存入到unattachedCategories()中的hashtable中。
                //2. 如果cls之前调用过realizeClass方法,那么直接将cat的方法加入到cls中。
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }
            
            if (cat->classMethods  ||  cat->protocols
                ||  (hasClassProperties && cat->_classProperties))
            {
                //1. 首相将cat存入到unattachedCategories()中的hashtable中。
                //2. 如果cls之前调用过realizeClass方法,那么直接将cat的方法加入到cls中。
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
            }
        }
    }
}

map_image阶段完成了类库的加载。

load_images

该函数的作用主要就是从已经加载的OC类中找到有load方法的类,并执行其中的load方法。

这里的功能比较简单,不做分析。