带你了解分类的加载流程
2020/1/22 23:27:14
本文主要是介绍带你了解分类的加载流程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一.探索前需知
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
做重映射。rw
、ro
等操作。Category
,包括Class
和Meta Class
。1.3 什么是非懒加载类和懒加载类?
我们看到map_images里有一个步骤是初始化所有非懒加载类,进行rw
、ro
等操作.那么到底什么情况会初始化非懒加载类?我们现在一个工程里创建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 懒加载的类何时读取并进行rw
、ro
等操作?
懒加载的类是什么时候读取的呢,是你第一次用到的时候,也就是你第一次创建并使用的时候.也就是
看过我之前文章的盆友一定会知道,它在底层会发送消息,会先进行快速查找和慢速查找流程,第一次进来的话会走慢速查找流程,来到 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结构:
此时类里面rw的method里有三个方法: cxx_destruct、 setName、getName,
而主类里是:
#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的赋值.
说明:文章中不恰当的地方,还希望看到的盆友提出,自己会及时改正
这篇关于带你了解分类的加载流程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值