Swift教程

带你了解分类的加载流程

本文主要是介绍带你了解分类的加载流程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一.探索前需知


 1.1 程序在启动时,编译好的mach-o文件内容如何加载到App内存里?

 其实上篇文章已经介绍了程序一运行如何将编译好的mach-o文件内容如何加载到App内存     里,一开始回来到 _objc_init(void),程序初始化函数,在这里会进行整个程序的一些环境配置、异常配置、静态构析函数配置等,然后会map_images读取镜像文件的过程.

1.2 map_images 做了哪些工作?

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

}

复制代码

  • 加载所有类到类的gdb_objc_realized_classes表中。
  • 对所有类做重映射。
  • 将所有SEL都注册到namedSelectors表中。
  • 修复函数指针遗留。
  • 将所有Protocol都添加到protocol_map表中。
  • 对所有Protocol做重映射。
  • 初始化所有非懒加载的类,进行rwro等操作。
  • 遍历已标记的懒加载的类,并做初始化操作。
  • 处理所有Category,包括ClassMeta Class
  • 初始化所有未初始化的类。

  • 1.3 什么是非懒加载类和懒加载类?

    我们看到map_images里有一个步骤是初始化所有非懒加载类,进行rwro等操作.那么到底什么情况会初始化非懒加载类?我们现在一个工程里创建LGPerson、LGStudent、LGTeacher 三个类.


    然后在待处理类里面打印出非加载的类:printf("non-lazy Class:%s\n",cls->mangledName());


    打印结果如下:


    发现打印了LGStudent、LGTeacher 和系统的一大堆类,但是LGPerson为什么没打印呢?

    原来LGStudent、LGTeacher里实现了+(void)load的方法,而LGPerson却没有实现+(void)load的方法.所以可以得出结论:非懒加载的类,就是实现了+(void)load的方法和静态实例变量,其实也好理解+(void)load 方法调用在main函数调用之前,所以系统肯定先帮你这个类先初始化了,所以就是非懒加载的,而懒加载的类就是没有实现+(void)load,系统不帮你加载,等到你什么时候用什么时候加载这就是懒加载的类.

    1.4 懒加载的类何时读取并进行rwro等操作?

    懒加载的类是什么时候读取的呢,是你第一次用到的时候,也就是你第一次创建并使用的时候.也就是


    看过我之前文章的盆友一定会知道,它在底层会发送消息,会先进行快速查找慢速查找流程,第一次进来的话会走慢速查找流程,来到 lookUpImpOrForward


    if (!cls->isRealized()) {
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
      }
    复制代码

    因为这是懒加载的类,之前并没有被实现所以必然回来到这个判断里来.

    realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
    {
        lock.assertLocked();
    
        if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
            // Non-Swift class. Realize it now with the lock still held.
            // fixme wrong in the future for objc subclasses of swift classes
            realizeClassWithoutSwift(cls);
            if (!leaveLocked) lock.unlock();
        } else {
            // Swift class. We need to drop locks and call the Swift
            // runtime to initialize it.
            lock.unlock();
            cls = realizeSwiftClass(cls);
            assert(cls->isRealized());    // callback must have provoked realization
            if (leaveLocked) lock.lock();
        }
    
        return cls;
    }
    复制代码

    看里面又来到了 realizeClassWithoutSwift,和非懒加载类后面的流程是一模一样的 .

     二.分类加载的初探

    2.1  分类的结构

    如果一个开发者没有底层objc的源码,怎么分析分类的结构,其实之前的文章也介绍这个方法 clang.

    首先我们在工程里创建个分类:


    然后打开终端 输入命令:clang -rewrite-objc LGTeacher+test.m -o LGteacherTest.cpp(输出个.cpp编译后的文件)


    发现这个文件里有 :

    static struct _category_t _OBJC_$_CATEGORY_LGTeacher_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) 

    test 代表类别的名字,_category_t 这个就是类别的结构体. 我们在objc源码里搜索c:

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    复制代码

    原来 category_t 底层是一个结构体 ,里面有name、cls、instanceMethods、classMethods、protocols、instanceProperties、_classProperties, name 就是类别的名字,

    cls就是类,instanceMethods 就是实例方法,classMethods就是类方法,protocols就是类别里的协议.

    2.2 分类的加载

    既然分类的结构已经清楚了,下面我们就要探索分类是如何加载到主类里面的.

    LGTeacher.h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGTeacher : NSObject
    @property (nonatomic, copy) NSString *name;
    
    + (void)sayMaster;
    
    @end
    
    NS_ASSUME_NONNULL_END
    复制代码

    LGTeacher.m

    #import "LGTeacher.h"
    
    @implementation LGTeacher
    
    + (void)load{
        NSLog(@"load");
    }
    
    + (void)sayMaster{
        NSLog(@"%s",__func__);
    }
    
    @end
    复制代码

    LGTeacher+test.h

    #import <AppKit/AppKit.h>
    
    
    #import "LGTeacher.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGTeacher (test)
    @property (nonatomic, copy) NSString *cate_p1;
    //@property (nonatomic, copy) NSString *cate_p2;
    
    //- (void)cate_instanceMethod1;
    - (void)cate_instanceMethod2;
    
    //+ (void)cate_classMethod1;
    + (void)cate_classMethod2;
    
    @end
    
    复制代码

     LGTeacher+test.m

    #import "LGTeacher+test.h"
    #import <objc/runtime.h>
    
    #import <AppKit/AppKit.h>
    
    @implementation LGTeacher (test)
    
    + (void)load{
        NSLog(@"分类 load");
    }
    
    - (void)setCate_p1:(NSString *)cate_p1{
    }
    
    - (NSString *)cate_p1{
        return @"cate_p1";
    }
    
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    
    + (void)cate_classMethod2{
        NSLog(@"%s",__func__);
    }
    @end
    复制代码

     有些盆友会说,探究前已经分析过 read_images 里有个方法,  

    // 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);
           }
       }
    }复制代码

     在这里会发现分类,然后走到:    

       // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : "");
                    }
                }
    
    复制代码

    因为category 里是实例方法,所以必然会走 if (cat->instanceMethods || cat->protocols

    || cat->instanceProperties) 这里面,addUnattachedCategoryForClass 添加没有加进去的分类,

    在这里进行添加的呗.那我们就来验证下, 首先我们在addUnattachedCategoryForClass) 这句代码中打上断点,


    来分析此时的class结构:



    此时类里面rwmethod里有三个方法: cxx_destructsetNamegetName,

    而主类里是:

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGTeacher : NSObject
    @property (nonatomic, copy) NSString *name;
    
    + (void)sayMaster;
    
    @end
    
    NS_ASSUME_NONNULL_END
    复制代码

    说明此时主类的方法以及被加载到rw里了,因为第五步是非懒加载类已经初始化过了,主类的rw、ro都有东西啦,有的盆友可能会问: + (void)sayMaster 没有加到呀,

    其实还是以前文章提到过的问题, 类方法添加到元类里,并不是本身的类里.

    我们接着往下分析进入到addUnattachedCategoryForClass 里面:


    这一步主要是将没有加载的categoryList 关联起来 并且加到NXMap 表里.

    紧接着 又来到(第五步非懒加载已经初始化过,判断里必走):

    if (cls->isRealized()) {
        remethodizeClass(cls);
        classExists = YES;
    }
    复制代码

    remethodizeClass  --> attachCategories -- >进入方法实现


    在这代码最后打下断点再分析 cls 结构:


    结构如下:





    分类里的方法是不是也加载到rw 里了,

    所有的一切一切都得到了验证,都是那么看得见摸得着, 那么分类的加载就分析完毕了吗NO,NO,NO 这才是冰山一角, 当前类、分类都实现了load 方法, 说明这是非懒加载类 非懒加载分类的搭配使用,还有更多情况请看下面.

     三.分类加载的进阶

    在上面我们介绍了 懒加载类和非懒加载类,那么同样分类也分懒加载和非懒加载,所以它们一起搭配使用就有四种情况:

     懒加载的分类(不实现 load 方法 )

     3.11  懒加载的分类(不实现 load 方法 )与非懒加载的类搭配使用(实现 load 方法 )

    我们知道非懒加载的类加载时,必然会走read_images - realizeClassWithoutSwift - methodlizeClass 


    我们这打个断点看看此时的类结构:



    我的天,此时ro里面有分类的方法、属性了,这该怎么解释呢? 其实这是因为在编译的时候已经把分类的信息给读取到ro里面了.

    3.12 懒加载的分类(不实现 load 方法 )与懒加载的类搭配使用 (不实现 load 方法 )

    自己实现懒加载 :Class cls = [LGTeacher class];

    懒加载的类第一次调用时,它在底层会发送消息,会先进行快速查找慢速查找流程,第一次进来的话会走慢速查找流程,来到 lookUpImpOrForward


    分析此时的LGPerson结构, 注意哦 我们上面调用的是对象方法,在方法查找中会在元类里进行查找,所以这个方法中cls 参数代表 元类, 我们来看下元类的结构:


    我的天, 元类的ro cate_classMethod2,也就是分类的方法.我把分类的代码粘一下

    #import "LGTeacher+test.h"
    #import <objc/runtime.h>
    
    #import <AppKit/AppKit.h>
    
    @implementation LGTeacher (test)
    
    //+ (void)load{
    //    NSLog(@"分类 load");
    //}
    
    - (void)setCate_p1:(NSString *)cate_p1{
    }
    
    - (NSString *)cate_p1{
        return @"cate_p1";
    }
    
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    
    + (void)cate_classMethod2{
        NSLog(@"%s",__func__);
    }
    @end
    
    复制代码

    lookUpImpOrForward 刚进来时 ,元类里的ro就已经有分类的方法了.由此可以得出结论:

    懒加载的分类 方法- 编译处理 - 直接处理 data() - ro


     非懒加载的分类(实现 load 方法 )

     3.21  懒加载的分类(实现 load 方法 )与非懒加载的类搭配使用(实现 load 方法 )

    上面初探时已经分析过,  read_images - realizeClassWithoutSwift - methodlizeClass - addUnattachedCategoryForClass - 判断是否实现 - 这里看到上面一行就在read_images 实现了:

    if (cls->isRealized()) {

     remethodizeClass(cls); -> 实现类信息

    } attachCategories 加载分类数据进来

    3.22  懒加载的分类(实现 load 方法 )与懒加载的类搭配使用(不实现 load 方法 )

    这种情况就比较特殊, 按之前分析的  懒加载的分类 会走到 read_images -->

    // 分类的处理 

      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);
           }
       }
    }复制代码

    懒加载的类之前分析会第一次调用时,它在底层会发送消息,会先进行快速查找慢速查找流程,第一次进来的话会走慢速查找流程,来到 lookUpImpOrForward.

    但在这里想一下:运行时分类都加载好了,主类第一次加载还要等到第一次消息发送时,是不是太慢了?

    所以这里和其它流程不一样的地方是:先走个 prepare_load_methods :

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertLocked();
    
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            
            const class_ro_t *ro = (const class_ro_t *)cls->data();
            const char *cname = ro->name;
            const char *oname = "LGTeacher";//LGTeacher
            // printf("类名 :%s \n",cname);
            if (cname && (strcmp(cname, oname) == 0)) {
    //            printf("prepare_load_methods :非懒加载分类名 :%s \n",cname);
            }
            
            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);
        }
    }
    复制代码

    在这个方法里面会有: realizeClassWithoutSwift(cls). 在这里进行初始化类信息, rw的赋值.

    四.总结

    本篇文章先介绍了懒加载类非懒加载类,然后分4种组合情况深入研究了类的加载分类加载的原理.其中: 

    1.分类的懒加载在编译期就已经确定了.

    2. 分类的非懒加载在read_images 在非懒加载分类中addUnattachedCategoryForClass 添加没有加进去的分类,最后remethodizeClass --> attachCategories,才会对rw进行操作. 

    3.类的非懒加载也是在read_images里进行非懒加载的类初始化.

    4.最后类的懒加载分为两种情况:如果分类也是懒加载的话,它在底层会发送消息,会先进行快速查找慢速查找流程,第一次进来的话会走慢速查找流程,来到 lookUpImpOrForward,进行初始化类,如果分类也是非懒加载的话,先走个 prepare_load_methods 在这个方法里面会有: realizeClassWithoutSwift(cls). 在这里进行初始化类信息, rw的赋值.

    说明:文章中不恰当的地方,还希望看到的盆友提出,自己会及时改正


    这篇关于带你了解分类的加载流程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!