IOS底层原理之类和分类加载

1,014 阅读17分钟

前言

前面已经探究了类的加载,类的加载分为懒加载类和非懒加载类,他们有不同加载流程,下面来探究下分类的加载,以及分类和主类之间加载不同的情况

准备工作

分类的加载

分类的底层结构是结构体categor_t,分类一般是加载到主类中的,什么时候加载以及加载的过程,这是下面要探究的

分类加载引入

WWDC类的优化中苹果为分类和动态添加专门分配的了一块内存rwe,因为rwe属于dirty memory所以肯定是需要动态开辟内存。下面从class_rw_t中去查找相关rwe的源码

struct class_rw_t {
    ... //省略部分代码
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        // 判断rwe是否存在
        if (fastpath(v.is<class_rw_ext_t *>())) {
            //如果已经有rwe直接返回地址指针
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            //为rwe开辟内存并且返回地址指针
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }
    class_rw_ext_t *deepCopy(const class_ro_t *ro)
        return extAlloc(ro, true);
    }
    ... //省略部分代码
}

源码中显示如果你主要用到rwe,肯定会调用extAllocIfNeeded()。全局搜索extAllocIfNeeded(),发现调用的地方很多有attachCategoriesdemangledNameclass_setVersionaddMethods_finishclass_addProtocol_class_addPropertyobjc_duplicateClass方法。从这些方法中发现一些是动态添加的方法,关于分类的方法,还有一些关于类名、设置类的版本,以及修复类。今天探究分类的加载流程,所以定位到attachCategories

attachCategories 反推法

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t 
cats_count, int flags)
{   ... //省略部分代码
    constexpr  uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];
    uint32_t mcount = 0;
    // 获取rwe
    auto rwe = cls->data()->extAllocIfNeeded();
    ...
    //遍历所有的分类,但是分类中的方法一般不要超过64个分类,不然苹果会认为你这个人有问题
    for (uint32_t i = 0; i < cats_count; i++) {
      auto& entry = cats_list[i];

      method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
    if (mlist) {
        //如果你的分类的个数超超过64个那么把这64个分类的方法列表加载到主类中
        //把后面的数据继续放到 mlists[]中
        if (mcount == ATTACH_BUFSIZ) {
            prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
            rwe->methods.attachLists(mlists, mcount);
            mcount = 0; //mcount 设置为0 
        }
        //如果 mcount = 0,mlist存放的位置在63个位置,总共是0 ~ 63
        mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
        fromBundle |= entry.hi->isBundle();
    }
      //属性的
      property_list_t *proplist =
        entry.cat->propertiesForMeta(isMeta, entry.hi);
    if (proplist) {
        if (propcount == ATTACH_BUFSIZ) {
            rwe->properties.attachLists(proplists, propcount);
            propcount = 0;
        }
        proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
    }
    //协议的
    protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
    if (protolist) {
        if (protocount == ATTACH_BUFSIZ) {
            rwe->protocols.attachLists(protolists, protocount);
            protocount = 0;
        }
        protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
    }
}

if (mcount > 0) {
    //在将分类方法添加到主类之前将方法进行排序
    //地址偏移还记得d+n嘛 mlists = d n = ATTACH_BUFSIZ - mcount
    //mlists + ATTACH_BUFSIZ - mcount =  d + n
    //此时的mlists + ATTACH_BUFSIZ - mcount 是一个二维指针,里面存放的是方法列表的首地址
    prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                       NO, fromBundle, __func__);
    //在将分类方法添加到主类方法中
    rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
    if (flags & ATTACH_EXISTING) {...}
} 
    
    //在将分类属性添加到主类属性中
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    //在将分类协议添加到主类协议中
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
     ... //省略部分代码
} 

attachCategories准备分类的数据,然后调用attachLists将数据添加到主类中,那么到底哪些地方调用attachCategories方法,全局搜索attachCategories

image.png

源码显示attachToClass调用attachCategories方法

image.png

源码显示load_categories_nolock调用attachCategories方法

attachToClass流程

全局搜索attachToClass,只有methodizeClass调用了attachToClass

static void methodizeClass(Class cls, Class previously)
{   ... //省略部分代码
    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                   ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
    ... //省略部分代码
  • methodizeClass这个方法很熟悉realizeClassWithoutSwift调用,而previously条件决定是否调用条件里面的attachToClass方法,而previously是作为参数传进来的。最终找到是realizeClassWithoutSwift传进来的,全局搜索realizeClassWithoutSwift,调用的地方传入的previously = nil,所以attachToClass里面的previously条件不会走,只能最后的attachToClass方法
  • previously作为备用参数,这种设计可能是苹果内部调试用的
  • attachToClass流程:_read_images --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories --> attachLists

load_categories_nolock流程

全局搜索load_categories_nolock

static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);
    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}

loadAllCategories方法中调用load_categories_nolock。全局搜索loadAllCategories

static bool  didInitialAttachCategories = false;

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);
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

load_images调用loadAllCategoriesload_imagesdyld中调用

  • didInitialAttachCategories默认是false,当执行完loadAllCategories()后自动将didInitialAttachCategories设为true,其实就是只调用一次loadAllCategories()
  • objcdyld完成注册回调后didCallDyldNotifyRegister = true
  • load_categories_nolock流程:load_images --> loadAllCategories --> load_categories_nolock --> load_categories_nolock --> attachLists

attachLists

attachLists是核心方法,attachLists作用是将分类数据加载到主类中

 void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        //hasArray()如果array()存在进入判断
        if (hasArray()) {
            // many lists -> many lists
            // oldCount = 获取array()->lists的个数
            uint32_t oldCount = array()->count;
            // 新的个数 =  oldCount + 新添加的
            uint32_t newCount = oldCount + addedCount;
            // 根据`newCount`开辟内存,类型是 array_t
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            //设置新数组的个数等于`newCount`
            newArray->count = newCount;
            //设置原有数组的个数等于`newCount`
            array()->count = newCount;
            //遍历原有数组中list将其存放在newArray->lists中 且是放在数组的末尾
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            //遍历二维指针`addedLists`中的list将其存放在newArray->lists中 且是从开始 
            //的位置开始存放 
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            //释放原有的array()
            free(array());
            //设置新的 newArray
            setArray(newArray);
            validate();
        }
        //如果主类中有方法,第一次list主类中的方法
        //如果主类中没有方法,则进来的就是分类方法
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        //当list时一维数组时,此时进入下面的判断创建`array_t`类型的结构体类型
        //`array_t`的lists存放的是各个分类数组的地址指针
        else {
            // 1 list -> many lists
            // 将list数组赋值给oldList 
            Ptr<List> oldList = list; 
            //oldList 存在 oldCount = 1
            uint32_t oldCount = oldList ? 1 : 0; 
            //新的newCount = 原有的count + 新增的count
            uint32_t newCount = oldCount + addedCount;
            //根据`newCount`开辟内存,类型是 array_t, array()->lists是一个二维数组
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            //设置数组的个数
            array()->count = newCount;
            //将原来的list放在数组的末尾
            if (oldList) array()->lists[addedCount] = oldList;
            //遍历addedLists将遍历的数据从数组的开始位置存储
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

源码分析,attachLists总共分三步流程

0 list --> 1 list

  • addedLists[0]的指针赋值给list

1 list --> many lists

  • 计算旧的list的个数
  • 计算新的list个数 ,新的list个数 = 原有的list个数 + 新增的list个数
  • 根据newCount开辟相应的内存,类型是array_t类型,并设置数组setArray
  • 将原有的list放在数组的末尾,因为最多只有一个不需要遍历存储
  • 遍历addedLists将遍历的数据从数组的开始位置存储

many lists --> many lists

  • 判断array()是否存在
  • 计算原有的数组中的list个数array()->lists的个数
  • 新的newCount = 原有的count + 新增的count
  • 根据newCount开辟相应的内存,类型是array_t类型
  • 设置新数组的个数等于newCount
  • 设置原有数组的个数等于newCount
  • 遍历原有数组中list将其存放在newArray->lists中 且是放在数组的末尾
  • 遍历addedLists将遍历的数据从数组的开始位置存储
  • 释放原有的array()
  • 设置新的newArray

注意List* const * addedLists二级指针。 就像 LWPerson * p = [LWPerson alloc], p叫做一级指针&p就叫二级指针

setArrayhasArray的探究

    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };

    bool hasArray() const {
        return arrayAndFlag & 1;
    }

    array_t *array() const {
        return (array_t *)(arrayAndFlag & ~1);
    }

    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1;
    }

    void validate() {
        for (auto cursor = beginLists(), end = endLists(); cursor != end; cursor++)
            cursor->validate();
    }
  • setArray()方法 arrayAndFlag = (uintptr_t)array | 1array异或1arrayAndFlag的第0位一定是1
  • hasArray()方法 arrayAndFlag & 1。如果arrayAndFlag的第0位是1,就返回YES,否则返回NO
  • 所以只要调用setArray()方法,不释放。hasArray()就是YES,和上面的3个流程对应起来

attachLists 流程分析图

image.png

实例验证 attachLists

实例验证attachLists不可能绕过attachCategories,前面只是简单介绍了attachCategories具体的细节没有探究,下面断点调试验证下

验证 attachCategories

创建主类LWPerson和分类LWA

@implementation LWPerson
+(void)load{
  NSLog(@"我是主类LWPerson");
} 
-(void)sayHello{ 
  NSLog(@"sayHelloP");
} 
@end
 
@implementation LWPerson (LWA) 
+(void)load{ 
  NSLog(@"我是分类LWA"); 
}
- (void)AsayNB{
  NSLog(@"我来了");
}
@end

运行源码,进行调试 image.png

图中显示加载的是LWA分类的方法列表,看下mlists的结构

image.png

mlists中最后一位存方的是LWA分类的方法列表的地址,mlists是一个二维指针类型

image.png mlists + ATTACH_BUFSIZ - mcount就是地址偏移和前面探究的d+n是一样的。mlists是首地址,ATTACH_BUFSIZ - mcount具体的第几个位置

验证attachLists

实例验证准备用 查看macho + 动态调试进行相互验证,在验证之前先补充下怎么读取macho文件 的,源码中出现_getObjc2ClassList_getObjc2NonlazyClassList等方法,进入方法看看具体实现

#define GETSECT(name, type, sectname)                                   \
    type *name(const headerType *mhdr, size_t *outCount) {              
        return getDataSection<type>(mhdr, sectname, nil, outCount);     \ 
    }                                                                   \
    type *name(const header_info *hi, size_t *outCount) {               \
        return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
    }

//      function name                 content type     section name
//refs结尾的都是需要修复的类和方法等
GETSECT(_getObjc2SelectorRefs,        **SEL**,          "__objc_selrefs"); 

GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 

GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");

GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
//macho section 等于__objc_classlist   所有类的列表(不包括分类)
GETSECT(_getObjc2ClassList,           classref_t **const**,      "__objc_classlist");
//macho section 等于__objc_nlclslist   懒加载类的列表
GETSECT(_getObjc2NonlazyClassList,    classref_t **const**,      "__objc_nlclslist");
//macho section 等于__objc_catlist     分类的列表
GETSECT(_getObjc2CategoryList,        category_t * **const**,    "__objc_catlist");
//macho section 等于__objc_catlist2    分类的列表
GETSECT(_getObjc2CategoryList2,       category_t * **const**,    "__objc_catlist2");
//macho section 等于__objc_nlcatlist   懒加载分类
GETSECT(_getObjc2NonlazyCategoryList, category_t * **const**,    "__objc_nlcatlist");
//macho section 等于__objc_protolist   协议列表
GETSECT(_getObjc2ProtocolList,        protocol_t * **const**,    "__objc_protolist");
//macho section 等于__objc_protolist   协议修复列表
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
//macho section 等于__objc_init_func   __objc_init初始化方法列表
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");

__objc_nlclslist对应macho文件(准确的说是可执行文件)的section的名字

image.png

macho文件左边section的名称对应着右边数据的列表,图中应该很清晰了

01

如果rwe有值肯定会调用extAllocIfNeeded方法,调用extAllocIfNeeded的时机,动态添加数据或分类的数据动态添加到主类,现在研究分类的情况。进入extAllocIfNeeded方法

class_rw_ext_t *extAllocIfNeeded() {
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}

rwe不存在就会去开辟内存调用extAlloc方法

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();
    //为rwe开辟内存
    auto rwe = objc::zalloc<class_rw_ext_t>();
    rwe->version = (ro->flags & RO_META) ? 7 : 0;
    //如果主类有方法,那么就会将主类的方法列表进行`attachLists`此时是 0 对 多
    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }
    //如果主类有属性,那么就会将主类的属性列表进行`attachLists`此时是0 对 多
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
     //如果主类有协议,那么就会将主类的协议列表进行`attachLists`此时是 0 对 多
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
    //设置rwe
    set_ro_or_rwe(rwe, ro);
    //返回rwe
    return rwe;
}

源码显示如果主类存在方法,那么就将方法进行attachLists,如果主类中没有方法就此时什么都不做。此时就会出现两种情况主类有方法和主类无方法

主类有方法,分类有方法

创建主类LWPersonLWTeacher,创建分类LWPerson+LWBLWPerson+LWA代码如下

@implementation LWTeacher
- (void)sayHello{
    NSLog(@"sayHelloT");
}
@end

@implementation LWPerson
+(void)load{
    NSLog(@"我是主类LWPerson");
}
-(void)sayHello{
    NSLog(@"sayHelloP");
}
@end

@implementation LWPerson (LWB)
+(void)load{
   NSLog(@"我是分类LWB");
}
- (void)BsayHello{
    NSLog(@"我来了");
}
@end

@implementation LWPerson (LWA)
+(void)load{
    NSLog(@"我是分类LWA");
}
- (void)AsayNB{
    NSLog(@"我来了");
}
@end

运行objc源码定位到attachCategories方法,因为现在研究的是分类

image.png

图中的第一个断点先断住然后在进入第二个断点,目的是过滤其它类。然后进入extAllocIfNeeded方法

image.png

此时rwe还没有值所以需要开辟内存走extAlloc方法

image.png

此时list有值,所以调用attachLists方法,进入attachLists方法

image.png

  • 断点进入01的判断中
  • 通过lldb调试,是主类的方法列表

主类没有方法,分类有方法

sayHello方法从LWPerson移除,重复上面的调试步骤

@implementation LWPerson
+(void)load{
    NSLog(@"我是主类LWPerson");
}
@end

image.png

按照上面的调试流程,进入extAlloc方法,此时的list = NULL,所以不会调用attachLists方法,但是要想动态添加到主类01这个流程流程一定要进入的,接着调试

image.png

attachCategories的第二个参数是对分类进行了一层包装,包装成locstamped_category_t类型 通过lldb调试里面分类是LWA分类,接着往下调试

image.png

此时会调用attachLists方法,进入attachLists方法

image.png

进入01流程的是LWA中的方法列表,大家可能奇怪为啥是加载LWA中的方法列表而不是LWB中 这个和分类的编译顺序有关,按编译的先后顺序进行加载

image.png 如果把分类LWB放在分类LWA前面先编译那么LWB就会先加载

总结

01流程有两种情况

  • 主类有方法,分类有方法:以主类的方法为基础将分类的方法加载到主类中
  • 主类没有方法,分类有方法:以第一个编译的分类为基础将其它分类一起合并,最后加载到主类中

1

现在把sayHello方法添加到LWPerson中,运行源码

image.png

把分类LWB放在分类LWA前面先编译那么LWB就会先加载,进入attachLists方法

image.png

list是主类中的方法列表,addedLists中存放着分类的指针,从addedLists取出分类中数据,现在看合并后的数据

image.png

array()->lists中存放着两个方法列表的地址, 分类的方法列表是是放在前面的

按照逻辑下一步要进入的流程,继续进行断点调试

image.png

此时加载的是LWA分类,进入attachLists方法

image.png

hasArray()=ture进入到多对多流程,addedLists二级指针中只有一个方法列表是LWA分类的

image.png newArraylists中存放3个方法列表,分别是分类LWA的方法列表,分别是分类LWB的方法列表以及主类的方法列表。最后编译的分类是在整个lists的最前面

类和分类的搭配

非懒加载类 + 非懒加载分类

非懒加载类的加载流程 非懒加载类实现load,非懒加载分类实现load。非懒加载类的数据加载是通过_getObjc2NonlazyClassList方法从macho文件获取,非懒加载分类的数据加载是通过_getObjc2NonlazyCategoryList方法从macho文件获取

image.png 非懒加载类获取数据示意图

image.png 非懒加载分类获取数据示意图

  • 非懒加载类加载流程:map_images --> map_images_nolock --> _read_images --> realizeClassWithoutSwift --> methodizeClass --> attachToClass
  • 非懒加载分类加载流程:load_images --> loadAllCategories --> load_categories_nolock --> attachCategories --> attachLists

日志打印示意图 image.png

非懒加载类 + 懒加载分类

image.png

  • 非懒加载类还是走map_images流程
  • 懒加载分类没有走attachCategories,那么分类中方法列表是什么时候加载的呢

image.png macho中分类的列表是没有数据的,那就说明不可能是动态时加载分类的数据,那么到底在什么时间去加载分类的数据呢
猜想:不在动态是添加,那么最有可能是在编译期,在加载类的时候,去获取ro中的方法列表,看看有没有分类的数据

image.png ro中不仅有主类的方法,同时还有分类的方法。ro是在编译期就确定的,也就是说懒加载分类中的数据在编译期就已经合并到了主类中。而且分类的数据也是放在主类的方法前面

懒加载类 + 懒加载分类

image.png

打印信息显示加载类没有走map_images流程,表示没有LWPeson没有在非懒加载列表中

image.png

macho中没有非懒加载类的数据列表和非懒加载分类的数据列表,那么类是在什么时候加载的呢?在realizeClassWithoutSwift添加断点,查看堆栈信息

image.png

堆栈信息显示是通过消息慢速查找流程调用了realizeClassWithoutSwift方法,一切就是那么的有缘,有些信息明明之间关联起来了,这就是缘分

那么分类的数据是在什么时候加载的呢?很明显是在编译时。验证下

image.png

懒加载类的流程是第一次发消息的时会进行类的加载,而懒加载分类的数据是在编译时就合并到ro

懒加载类 + 非懒加载分类

这种情况流程比较复杂,因为非懒加载分类的个数是对整个加载流程是有影响的

懒加载类 + 一个非懒加载分类

image.png

这种方式和非懒加载类 + 懒加载分类是一样的,非懒加载分类强制把懒加载类,加载提前到非懒加载类加载的时候,而且编译时也是把懒加载类变成了非懒加载类,然后非懒加载的分类的数据合并到了主类中

image.png

macho文件数据展示已经很明显了,下面看下是不是在分类合并在ro

image.png

懒加载类变成非懒加载类,分类的数据在编译期间合并到ro

懒加载类 + 多个非懒加载分类

image.png 打印的信息分析:主类的加载没有走map_images流程,调用两次load_categories_nolock说明是有两个分类,但是最后没有走attachCategories方法,而是走realizeClassWithoutSwift加载主类,然后调用attachCategories流程。在整个流程中需要解决两个问题

  • 分类加载过程中没有走attachCategories方法,那么它的流程是什么
  • 怎么调用到realizeClassWithoutSwift流程的

image.png

macho中分类列表和非懒加载分了列表,有LWB分类和LWA分类,但是没有非懒加载类列表

load_categories_nolock添加断点,运行源码

image.png

cls如果初始化则走attachCategories方法,如果没有则走unattachedCategories.addForClass方法。进入addForClass方法

void addForClass(locstamped_category_t lc, Class cls)
{
    runtimeLock.assertLocked();

    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: found category %c%s(%s)",
                     cls->isMetaClassMaybeUnrealized() ? '+' : '-',
                     cls->nameForLogging(), lc.cat->name);
    }
    // 先到哈希表中的去查找又没有lc
    auto result = get().try_emplace(cls, lc);
    // 如果有 判断result.second 是否有数据,没有将lc存入result.second
    if (!result.second) {
        result.first->second.append(lc);
    }
}
  • 首先到哈希表中根据keycls是查找lclc是系统底层统一封装的数据类型
  • 如果表中有lc,判断lc.second是否有数据,如果没有则赋值
  • 注意现在的分类数据直接存在哈希表中和类现在没有关系。这点很重要

探究下怎么去加载类的在realizeClassWithoutSwift添加断点,运行源码

image.png

堆栈信息显示是load_images --> prepare_load_methods -->realizeClassWithoutSwift 探究下prepare_load_methods的具体实现

void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertLocked();
//从macho中获取类的非懒加载列表
classref_t const *classlist = 
    _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
    //将重新映射的类添加到load列表中
    schedule_class_load(remapClass(classlist[i]));
}
//从macho中获取非懒加载类的列表
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列表中
    add_category_to_loadable_list(cat);
  }
}
  • macho中获取非懒加载类的列表
  • 将重新映射的类添加到类的load列表中
  • macho中获取非懒加载分类列表
  • 类的初始化加载
  • 将分类添加到分类的load列表中

探究下schedule_class_load

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;
    //递归`cls`的父类
    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());
    //将类还有父类都添加到load表中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); `IMP`
}

add_category_to_loadable_listadd_class_to_loadable_list基本一样

{
 ...//省略部分代码
 //获取类中load方法的IMP
 method = cls->getLoadMethod();
 //类的load表
 loadable_classes[loadable_classes_used].cls = cls;
 loadable_classes[loadable_classes_used].method = method;
 loadable_classes_used++;
 ... 

 ... 
 //获取分类中load方法的IMP
 method = _category_getLoadMethod(cat);
 //分类的load表
 loadable_categories[loadable_categories_used].cat = cat;
 loadable_categories[loadable_categories_used].method = method;
 loadable_categories_used++;
 ... 
 } 

add_category_to_loadable_listadd_class_to_loadable_list保存的是封装的类型,这个类型有两个变量一个是cls保存类,一个是method保存的是load方法的IMP

探究realizeClassWithoutSwift后续的流程,根据打印的消息是流程是realizeClassWithoutSwift --> attachToClass --> attachCategories

void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
       (flags & ATTACH_METACLASS) ||
       (flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();

//在哈希表中key = previously(cls)对应的一张表
//这张表中存放者分类的数据
auto it = map.find(previously);
//如果it和表中的最后一个数据不相等进入判断,最后一个算是标识位
if (it != map.end()) {
    // 获取分类的数据list的地址
    category_list &list = it->second;
    if (flags & ATTACH_CLASS_AND_METACLASS) {
        //给元类类加载分类数据
        int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
        attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
        attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
    } else {
        //给类加载分类数据
        attachCategories(cls, list.array(), list.count(), flags);
    }
    //释放表中cls对应表的数据
    map.erase(it);
}

attachToClass中断点调试

image.png

it != map.end()成立进入判断流程,只要哈希表中存分类的数据条件就成立,下面探究下it中到底在哪里存了分类的数据

image.png

分类的数据存在了底层的表中,当需要把分类的数据加载到主类的时候,就从表中获取,加载完以后清空对应表中分类的数据

总结

类的加载很复杂很复杂每个地方单独拿出来都能写一篇博客,现在只能整理下整体的流程。底层的探索任然在继续,这就是痛并快乐着