OC底层->Category 、+load、+initialize

572 阅读3分钟

创建一个Category 分类

image.png

  • 简单调用和实现 image.png
  • 生成一个cpp文件 image.png
  • 关闭cpp文件参与编译 image.png
  • 生成一个这样的结构体 有几个分类就会在编译时生成几个结构体对象。 image.png
  • 源码中的定义

image.png

  • instance_methods 对象方法实例方法

  • class_methods 类方法

  • protocol 协议

  • properties 属性

  • 存储的数据 image.png

  • 源码核心代码区 image.png

  • 添加方法、协议、成员属性 image.png

image.png

image.png

  • 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;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

  • 取出老数组的数量+新数组数量
  • 创建新的数组 数组扩容 image.png
  • 现在分析一下这里的结构 原来用的时一个函数 memmove 显然新的源码已经没有了
    • 1.假设oldCount = 1,addedCount = 2,newCount = 3
    • 2.扩容成了数量为3的新数组 下标 0,1,2,
    • 3.开始对oldCount的for循环 oldCount=1 就循环了一次
    • 4.array()->lists[i] 取出数据 i = 0,可以得知取数据也是从后面还是去的,因为加入odlCount = 2,i=1,取数据也是从下标大的开始的。
    • 5.注意这个时候加到新数组的下标是 i+addedCount = 2,所以原来old的就放到了下标2处的位置
    • 6.循环addedCount的时候就是正常的0++,下标0和下标1就一次存放了
    • 7.所以得出结论调用了这个方法后,原来的方法,协议,成员属性都会被放到数组的后面了

image.png

  • 分类1先参与的编译运行时将原先方法挤到后面去,优先在类对象的方法列表中找到分类1的方法。

  • 分类2再参与编译运行时,原先的1就变成了old了,分类2又把分类1的方法挤到后面去了 image.png

  • 控制编译顺序 image.png

  • objc_msgSend

Category的加载处理过程

  • 1.通过Runtime加载某个类的所有Category数据

  • 2.把所有Category的方法、属性、协议数据,合并到一个大数组中

    • 后面参与编译的Category数据,会在数组的前面
  • 3.将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

  • 源码解读顺序

    • objc-os.mm

      • _objc_init
      • map_images
      • map_images_nolock
    • objc-runtime-new.mm

      • _read_images
      • remethodizeClass
      • attachCategories
      • attachLists
      • realloc、memmove、 memcpy

load

  • load 方法是直接获取方法的地址进行调用的 image.png

image.png

image.png

  • +load方法会在runtime加载类、分类时调用

  • 每个类、分类的+load,在程序运行过程中只调用一次

  • 调用顺序

    • 先调用类的+load

    • 按照编译先后顺序调用(先编译,先调用)

    • 调用子类的+load之前会先调用父类的+load

    • 再调用分类的+load

    • 按照编译先后顺序调用(先编译,先调用)

  • objc4源码解读过程:objc-os.mm

  • _objc_init

  • load_images

  • prepare_load_methods

  • schedule_class_load

  • add_class_to_loadable_list

  • add_category_to_loadable_list

  • call_load_methods

  • call_class_loads

  • call_category_loads

  • (*load_method)(cls, SEL_load)

  • +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

initialize

  • +initialize方法会在类第一次接收到消息时调用

  • 调用顺序

    • 先调用父类的+initialize,再调用子类的+initialize
    • (先初始化父类,再初始化子类,每个类只会初始化1次)
  • +initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点

    • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    • 如果分类实现了+initialize,就覆盖类本身的+initialize调用
  • objc4源码解读过程

    • objc-msg-arm64.s

    • objc_msgSend

    • objc-runtime-new.mm

      • class_getInstanceMethod
      • lookUpImpOrNil
      • lookUpImpOrForward
      • _class_initialize
      • callInitialize
      • objc_msgSend(cls, SEL_initialize)
  • 源码方法

  • 先判断父类有没有初始化 递归调用 image.png

  • 完成自己的初始化 image.png

  • 消息发送机制完成调用 image.png