[iOS] 类的加载(下)

在之前,理解了类是如何从Mach-O加载到内存的,这次来了解下分类是如何加载到类中的,以及分类和类搭配使用的情况。

1. 分类的本质

  • 在 main 中定义 LGPerson 的分类 LG
@interface LGPerson (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
+ (void)cate_classMethod;

@end

@implementation LGPerson (LG)

- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}

+ (void)cate_classMethod{
    NSLog(@"%s",__func__);
}

@end
  • 使用clang查看编译的 main.cpp文件:
clang -rewrite-objc main.m -o main.cpp 
image.jpeg

从上面图中可以看出,分类的类型是_category_t(倒数第二个是 0,表示没有协议,所以赋值为 0)

  • cpp文件中,搜索 struct _category_t,如下所示:
struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; 实例方法
    const struct _method_list_t *class_methods; 类方法
    const struct _protocol_list_t *protocols; 协议
    const struct _prop_list_t *properties; 属性
};
  • 搜索_CATEGORY_INSTANCE_METHODS_LGPerson_,找到其底层实现
    image.jpeg

其中有 3 个方法,格式为sel+签名+地址,是_objc_method结构体的属性:

image.jpeg

  • 继续搜索_objc_method,其中的对应关系如下:
struct _objc_method {
    struct objc_selector * _cmd; 对应 sel
    const char *method_type; 对应 方法签名
    void  *_imp; 对应函数地址
};

2. 分类的加载

2.1 创建分类

创建 LGPerson 的两个分类:LGA 和 LGB:


image.jpeg
2.2 加载过程

在之前介绍类的加载时提到了rwe的加载,其中分析了分类的data 数据是如何加载到类的,而且分类的加载顺序是:LGA ->LGB的顺序加载到类中,即越晚加进来,越在前面

其中查看了methodizeClass 的源码实现,可以发现类的数据和分类的数据是分开处理的,主要是因为在编译阶段,就已经确定好了方法的归属位置(即实例方法存储在类中,类方法存储在元类中),而分类是后面才加进来的。

image.png

其中分类需要通过attatchToClass添加到类,然后才能在外界进行使用,在此过程,我们已经知道了分类加载步骤的后面两个步骤,分类的加载主要分为 3 步:

  • 分类数据加载时机:根据类和分类是否实现 load 方法来区分不同的时机
  • attachCategories准备分类数据
  • attachLists 将分类数据添加到主类中
2.3 分类的加载时机
2.3.1 主类 LGPerson + 分类 LGA\LGB 均实现+load 方法

下面我们来探索一下分类数据的加载时机,以主类 LGPerson + 分类 LGA\LGB 均实现+load 方法为例。

通过第二步反推第一步的加载时机
在之前我们了解到,在走到 attachCategories 方法时,必然会有分类数据的加载,可以通过反推法查看在什么时候调用 attachCategories 的,通过查找,有两个方法中调用:

  • load_categories_nolock方法中

    image.jpeg

  • addToClass方法中,这里经过调试,在主类和分类都实现load方法时,是不会走到 if 流程中的:

    image.jpeg

  • 不加任何断点,运行objc代码,可以得出以下打印日志,通过日志可以发现addToClass方法的下一步就是load_categories_nolock方法就是加载分类数据:

    image.jpeg

  • 全局搜索load_categories_nolock的调用,有两次调用

    • 一次在loadAllCategories方法中

      image.jpeg

    • 一次在_read_images方法中

      image.jpeg

  • 但是经过调试发现,是不会走_read_images方法中的 if 流程,而是走的loadAllCategories方法中的

    image.jpeg

  • 全局搜索查看loadAllCategories的调用,发现是在load_images时调用的

    image.jpeg

  • 我们给 attachCategories 加自定义逻辑的断点,bt查看堆栈信息:

    image.jpeg

所以综上所述:该情况下的分类的数据加载时机的反推路径为:
attachCategories -> load_categories_nolock -> loadAllCategories -> load_images

而我们的分类加载正常的流程的路径为:
realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories

其中正向和反向的流程如下图所示:


image.png
2.3.2 主类 LGPerson + 分类 LGA 实现 +load 方法,LGB 不实现 +load 方法
  • 断点定在attachCategories中加自定义逻辑部分,一步步往下执行
    image.jpeg
  • 继续往下执行,会再次来到attachCategories方法中断住
    image.png

总结:只要有一个分类是非懒加载分类,那么所有的分类都会被标记为非懒加载分类,意思就是加载一次 已经开辟了rwe,就不会再次懒加载,重新去处理LGPerson

2.4 分类和类的搭配使用

通过上面的两个例子,我们可以大致将类 和 分类 是否实现+load的情况分为4种

类+分类 . .
. 分类实现+load 分类未实现+load
类实现+load 非懒加载类+非懒加载分类 非懒加载类+懒加载分类
类未实现+load 懒加载类+非懒加载分类 懒加载类+懒加载分类
2.4.1 非懒加载类 + 非懒加载分类

即主类实现了+load方法,分类同样实现了+load方法,在前文分类的加载时机时,我们已经分析过这种情况,所以可以直接得出结论,这种情况下:

  • 类的数据加载是通过_getObjc2NonlazyClassList加载,即ro、rw的操作,对rwe赋值初始化,是在extAlloc方法中
  • 分类的数据加载是通过load_images加载到类中的
2.4.2 非懒加载类 + 懒加载分类

类和分类的加载是在read_image就加载数据了,数据来自datadata在编译时期就已经完成,即data中除了类的数据,还有分类的数据,与类绑定在一起

2.4.3 懒加载类 + 懒加载分类

其数据加载推迟到 第一次消息时,数据同样来自data,data在编译时期就已经完成

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

只要分类实现了load,会迫使主类提前加载,即主类强行转换为非懒加载类样式,即在_read_images中不会对类做实现操作,需要在load_images方法中触发类的数据加载,即rwe初始化,同时加载分类数据

如下图所示:


image.png

3.load_images

load_images方法的主要作用是加载镜像文件,其中最重要的有两个方法:perpare_load_methods(加载) 和 call_load_methods (调用)

image.jpeg

3.1 perpare_load_methods

进入perpare_load_methods源码:

image.jpeg

-schedule_class_load方法,这里是根据类的继承链递归调用获取 load,直到cls不存在才结束递归,目的是为了确保父类的 load优先加载:

image.jpeg

  • 进入add_class_to_loadable_list,主要是获取 load 方法,然后将load方法cls类名一起加到loadable_classes表中:

    image.jpeg

  • 进入getLoadMethod,主要是获取方法的sel 为 load的方法:

    image.jpeg

-add_category_to_loadable_list
获取所有的非懒加载分类中的load方法,将分类名``+load方法加入表loadable_categories

image.jpeg

3.2call_load_methods

主要有 3 部分操作:

  • 反复调用类的+load,直到不再有

  • 调用一次分类的+load

  • 如果有类或更多未尝试的分类,则运行更多的+load

    image.jpeg

  • 进入call_class_loads,主要是加载类的load 方法

    image.jpeg

注意:方法中有两个隐藏参数,第一个为id 即self,第二个为sel,即cmd

  • call_category_loads,主要是加载一次分类的 load 方法
    image.png
3.3 总结

综上所述,load_images方法整体调用过程及原理图示如下:

  • 调用过程图示:


    image.jpeg

并且:
父类的 load 方法优先于子类调用
主类的 load 方法优先于分类调用
分类的 load 方法则取决于编译顺序。

  • 原理图示


    image.jpeg

主要分为两步:

  • 从所有的非懒加载类分类中的+load分别添加到表中
  • 调用类和分类的+load方法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容