类的加载

作者: 脚踏实地的小C | 来源:发表于2020-01-11 00:46 被阅读0次

我们将从_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();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

environ_init():环境变量的初始化。
tls_init():当前线程key的绑定。比如每个线程数据的析构函数;
static_init():运行C++静态构造函数。

static_init.png
       这个时候我们如果打个断点,会看到这个 count11 ,那么,这个 11 里面是否包含我们写的方法个数嘛?显然不是,它只是跟系统级别的构造函数有关。

lock_init():是空实现。可能是预留、未开源、工厂重写,或者能够跟 C++ 的通用。
exception_init():初始化libobjc的异常处理系统,注册相应异常的回调函数,监控回调。
      介绍了这么多的前戏,也该我们的主角_dyld_objc_notify_register(&map_images, load_images, unmap_image);登场了。
      其中_dyld_objc_notify_register仅供Objc运行时使用,注册处理程序,以便在映射、取消映射和初始化Objc图像时调用。
      &map_images:映射镜像文件,dyld将使用包含objc-image-info的镜像文件的数组回调mapped函数

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);
}
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;
    if (firstTime) {
        preopt_init();
    }
    hCount = 0;
    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) {
                continue;
            }
            if (mhdr->filetype == MH_EXECUTE) {
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
            }
            hList[hCount++] = hi;

        }
    }
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();
    }
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    firstTime = NO;
}

      这里你会发现和源码不太一样,主要是做了一些精简,我们只要知道一段代码主要的内容一般是在if else判断、whiledo while循环等,或者有 通过返回值来确定这个函数的功能 的代码块。毕竟 快速筛选核心代码才是王道
      这块代码里主要是计算出当前的count,然后通过_read_images读镜像文件。

那么_read_images里面会做些什么呢?

1、加载所有类到类的gdb_objc_realized_classes表中。
2、对所有类做重映射。
3、将所有SEL都注册到namedSelectors表中。
4、修复函数指针遗留。
5、将所有Protocol都添加到protocol_map表中。
6、对所有Protocol做重映射。
7、初始化所有非懒加载的类,进行rw、ro等操作.
8、遍历已标记的懒加载的类,并做初始化操作。
9、处理所有Category,包括ClassMeta Class
10、初始化所有未初始化的类。

从宏观角度来分析,可分为以下七大部分

1、第一次进来,需要一个容器来存储数据,所以开始创建表
     NXCreateMapTable所有类的表 -- 包括实现和没实现的
     NXCreateHashTable记录已经开辟后的类(和元类)的表
     两张表的目的有时候有些未初始化的类在当前的表(小)里找不到,尽管它在总表里面有加载分配,但它并不一定是已经初始化的表,主要是为了断开。为了能够精确使用,不用每次从总表里查找。
2、类处理
3、方法编号处理
4、协议处理
5、非懒加载处理
6、待处理的类
7、分类处理

void _read_images {
    
    // 1:第一次进来 - 开始创建表
    // gdb_objc_realized_classes : 所有类的表 - 包括实现的和没有实现的
    // allocatedClasses: 包含用objc_allocateClassPair分配的所有类(和元类)的表。(已分配)
    if (!doneOnce) {
           doneOnce = YES;
        // 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);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    }
    
    // 2:类处理
    for (i = 0; i < count; i++) {
      Class cls = (Class)classlist[i];
      Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    }
    
    // 3: 方法编号处理
    for (EACH_HEADER) {
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
          const char *name = sel_cname(sels[i]);
          sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }

    // 4: 协议处理
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        NXMapTable *protocol_map = protocols();
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);
        }
    }
    
    // 5: 非懒加载类处理
    for (EACH_HEADER) {
      classref_t *classlist =
          _getObjc2NonlazyClassList(hi, &count);
      addClassTableEntry(cls);
      realizeClassWithoutSwift(cls);
    }
    
    // 6: 待处理的类
    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);
    }
    
    // 7:分类处理
   for (EACH_HEADER) {
       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);
       }
   }
}

      Class cls = (Class)classlist[i];得到的cls是个地址,此时还没有类,只是从map中读取到这个类占用的地址,但是这个地址指向的空间还未确定。

类处理.png

      Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);readClass没有ro、rw进行处理,只是加入到总表里,并且因为已经分配了地址,所以也插入到HashTable中。添加过的不会再次添加readClass的关键代码如下:

addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);

     realizeClassWithoutSwift:只是对rw中的ro进行赋值,rw还是没处理。如图所示:

realizeClassWithoutSwift.png realizeClassWithoutSwift_2.png

      然后通过以下代码进行当前类的父类、元类进行实现( 递归 )。当是supercls时, 下次父类、元类通过addSubclass集成对等关系(双向链表

supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

cls->superclass = supercls;
cls->initClassIsa(metacls);

if (supercls) {
    addSubclass(supercls, cls);
  } else {
    addRootClass(cls);
  }
父类和元类.png

     最后通过methodizeClass这个真正的对rw进行处理,通过attachListsro中的复制到rw中。那么问题来了!

为什么已经有了ro还要rw呢?

     ro是万年不变的代码,只能够读取。但是有时候我们要进行外部的调试处理,这时我们就需要一个rw能够进行动态的添加处理。

attachLists是什么情况才会执行呢?

1、添加方法addMethods
2、添加属性_class_addProperty
3、添加协议class_addProtocol
4、分类的加载attachCategories

attachLists具体是怎么实现?

多对多

// many lists -> many lists
uint32_t oldCount = array()->count;//10
uint32_t newCount = oldCount + addedCount;//4
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;// 10+4
   
memmove(array()->lists + addedCount, array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            
memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));

0对一

 // 0 lists -> 1 list
list = addedLists[0];

一对多:

 // 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;
 if (oldList) array()->lists[addedCount] = oldList;
 memcpy(array()->lists, addedLists, 
 addedCount * sizeof(array()->lists[0]));

      其中memcpy快,memmove慢。

attachLists的实现.png

      处理时是将一维数组method_t打包成method_list_t直接插到二维数组method_array_t方便直接

method_array_t.png

总结

1、readclass:判断是不是后期要处理的类,插入到表;

2、realizeClassWithoutSwift读取class的data() -> ro/rw创建 -> 父类与元类的实现 -> 父类与元类的归属关系 -> 将此类链接到其超类的子类列表

3、methodizeClass:把ro的数据写入到rw

4、attachLists:有一对多0对一多对多三种情况。

相关文章

  • 第一章 类加载过程

    要点 类加载过程 类加载器 一、类加载过程 1.类的加载过程 类的加载 .class文件过程分为:加载---->连...

  • 深入理解jvm类加载机制

    1.什么是类加载? 类加载机制一个很大的体系,包括类加载的时机,类加载器,类加载时机。 1.1类加载过程 加载器加...

  • java基础知识之java类加载器

    1. 什么是类加载器 类加载器就是用来加载类的东西!类加载器也是一个类:ClassLoader 类加载器可以被加载...

  • 《深入理解JVM虚拟机》读书笔记-类加载器&Java模块化系统

    类加载器 一.类加载器 1.1 类与类加载器 类加载器的定义: Java虚拟机设计团队有意把 类加载阶段中 的“ ...

  • JVM类加载入门

    一 类加载顺序 class类加载-->验证-->准备--->解析--->初始化 class类加载:通过类加载器加载...

  • 学习笔记 | JAVA的反射(二)

    利用反射机制动态加载类 、获取类的方法、获取类的属性 编译时刻加载类是静态加载类,运行时加载类是动态加载类 正常创...

  • jvm类加载器详解和如何打破双亲委派机制

    类加载过程: 项目启动的时候,并不是加载项目中的所有类,是在使用的时候加载,类加载器加载类的时候首先加载父类,所以...

  • JVM - ClassLoader

    1. 概述 类加载器实际定义了类的namespace。 2.类加载方式之当前类加载器和指定类加载器 类的加载只有两...

  • java-类加载机制

    类的加载机制 主要关注点: 什么是类的加载 类的生命周期 类加载器 双亲委派模型 什么是类的加载 类的加载指的是将...

  • java类加载器及其原理

    java类加载器 : java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器) ...

网友评论

    本文标题:类的加载

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