一.探索前需知
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的赋值.
说明:文章中不恰当的地方,还希望看到的盆友提出,自己会及时改正