1.镜像加载:map_images --> map_images_nolock --> _read_images
_objc_init注入回调_dyld_objc_notify_register(&map_images, load_images, unmap_image)
可知镜像的加载在map_images
中
调用栈: map_images --> map_images_nolock --> _read_images
read_images主要功能
-
- 条件控制进行一次的加载
-
- 修复预编译阶段的
@selector
的混乱问题
- 修复预编译阶段的
-
- 错误混乱的类处理
-
- 修复重映射一些没有被镜像文件加载进来的类
-
- 修复一些消息
-
- 当我们类里面有协议的时候 : readProtocol
-
- 修复没有被加载的协议
-
- 分类处理
-
- 类的加载处理
-
- 没有被处理的类 优化那些被侵犯的类
2 类的加载
在_read_images函数中获取到classlist后进入循环,断点调试,在readclass之前打印cls,cls中没有名字,但经过readclass后再打印cls,发现cls已经有名字了,说明readclass
就是类加载的关键
注:怎么找重点----断点调试,判断和循环里设置断点,看程序会不会进断点,如果不进,可以直接过,只关注程序的运行流程
2.1 readClass分析
断点调试
- 定义 LGPerson
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;
- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;
+ (void)kc_sayClassMethod;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson.h"
@implementation LGPerson
//+ (void)load{
//
//}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
在main.m中调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person kc_instanceMethod1];
NSLog(@"%p",person);
}
return 0;
}
在readClass函数的if和for中设置断点,重点关注类的加载部分
在readClass函数里并没有进入类的ro,rw赋值的if中,而只是类名称的关联addNamedClass
和将类添加到表中addClassTableEntry
类的加载过程(ro,rw,rwe的赋值)在什么时候进行的?
realizeClassWithoutSwift在类加载实现之前,是以macho结构data形式存在的
app在使用类时,是需要在磁盘中app的二进制文件中读取类的信息,二进制文件中的类存储了类的元类、父类、flags和方法缓存
-
class_ro_t简称ro:read only,将类从磁盘中读取到内存中就是对ro的赋值操作。由于ro是只读的,加载到内存后不会发生改变又称为clean memory(干净内存)。
-
class_rw_t简称rw:read write,用于读写编写程序。 drity memory 在进程运行时发生更改的内存。类一经使用运行时就会分配一个额外的内存,那么这个内存变成了drity memory。
-
class_rw_ext_t简称rwe:read write ext,用于运行时存储类的方法、协议和实例变量等信息。在实际应用中,类的使用量只是10%,这样就在rw中造成了内存浪费,所以苹果就把rw中的方法、协议和实例变量等放到了class_rw_ext_t中
-
获取ro
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;//如果存在rwe,则从rwe中取ro
}
return v.get<const class_ro_t *>();//如果不存在rwe,则从rw中取ro
}
-
什么时候需要rwe 在分析attachCategrories的源码时,知道
rwe = cls->data()->extAllocIfNeeded()
可知extAllocIfNeeded()是开辟rwe我们可以直接全部搜索extAllocIfNeeded,看那些地方可以是开发者控制的
- attachCategories 添加分类
- addMethod 添加方法
- class_addProtocol 添加协议
- _class_addProperty 添加属性
2.2 realizeClassWithoutSwift(类的加载)调用时机
-
回到_read_images函数,往函数下文看,看到一个熟悉的函数
realizeClassWithoutSwift
,从函数名就可知道是实现类,断点验证下---·但在_read_images中并没有调用realizeClassWithoutSwift
-
进入到
realizeClassWithoutSwift的源码中查看
,确实是类的实现操作 从类的MachO结构中读取到data(),给ro和rw赋值 -
可以在
reaizeClassWithoutSwift 设置断点
,并定位需要关注的类
,探索什么时候调用reaizeClassWithoutSwift -
断点执行顺序,可知是在main函数中调用
LGPerson *person = [LGPerson alloc]
之后,才调用reaizeClassWithoutSwift
, 其调用堆栈为 -
reaizeClassWithoutSwift的调用时机是在消息发送的慢速查找
lookUpImpOrForward
中, 消息发送之前会先判断类是否实现,cls = initializeAndLeaveLocked(cls, inst, runtimeLock)
----这是OC中著名的懒加载机制,将类的加载推迟到第一次方法调用的时候
问题 : 什么时候从_read_images中的realizeClassWithoutSwift函数来实现类的加载呢? 我们可以在LGPerson中实现
+load函数
,再断点_read_images中的realizeClassWithoutSwift
函数,可以看到断点可以卡主,在进入main函数之前就进行了LGPerson类的加载
2.3 懒加载类与⾮懒加载类 — 当前类是否实现 load ⽅法
- 懒加载类情况 数据加载推迟到第⼀次消息的时候(方法的慢速查找时判断类是否实现)
调用堆栈: [LGPerson alloc] --> objc_alloc -->callAlloc --> _objc_msgSend_uncached -->lookUpImpOrForward -->initializeAndLeaveLocked-->initializeAndMaybeRelock-->realizeClassMaybeSwiftAndUnlock-->realizeClassMaybeSwiftMaybeRelock --> realizeClassWithoutSwift
- ⾮懒加载类情况 map_images的时候 加载所有类数据
调用栈:
_dyld_start --> _objc_init --> _dyld_objc_notify_register --> dyld::registerObjcNotifiers --> dyld::notityBatchPartial --> map_images -->map_images_nolock --> _read_images --> realizeClassWithoutSwift
3 methodizeClass分析(方法化当前的类)
realizeClassWithoutSwift函数类的data加载完成后,会进入methodizeClass函数
,对方法、属性、协议的处理和排序等
,关键函数调用栈:methodizeClass --> prepareMethodLists --> fixupMethodList
4 load_images解析
load_images
方法的主要作用是加载镜像文件,其中最重要的有两个方法:prepare_load_methods
(加载) 和 call_load_methods
(调用)
-
进入
prepare_load_methods
源码- 进入
_getObjc2NonlazyClassList -> schedule_class_load
源码,这里主要是根据类的继承链递归调用获取load
,直到cls不存在才结束递归,目的是为了确保父类的load优先加载
- 进入
add_class_to_loadable_list
,主要是将load方法和cls类名
一起加到loadable_classes
表中 _getObjc2NonlazyCategoryList -> realizeClassWithoutSwift -> add_category_to_loadable_list
,主要是将非懒加载分类的load方法加入表中- 进入
add_category_to_loadable_list
实现,获取所有的非懒加载分类中的load方法,将分类名+load
加入表loadable_categories
- 进入
-
进入
call_load_methods
源码,主要有3部分操作- 反复调用
类的+load
,直到不再有 调用一次分类的+load
- 如果有类或更多未尝试的分类,则运行更多的+load
- 进入
call_class_loads
,主要是加载类的load方法
, 其中load
方法中有两个隐藏参数,第一个为id 即self
,第二个为sel,即cmd
call_category_loads
,主要是加载一次分类的load方法
- 反复调用
总结:
类方法和分类方法重名 调用哪个?
-
如果同名方法是
普通方法
,包括initialize
-- 先调用分类方法- 因为
分类的方法是在类realize之后 attach进去的
,插在类的方法的前面,所以优先调用分类的方法
(注意:不是分类覆盖主类!!) initialize
方法什么时候调用?initialize
方法也是主动调用,即第一次消息时
调用,为了不影响整个load,可以将需要提前加载的数据
写到initialize
中
- 因为
-
如果同名方法是
load
方法 -- 先主类load
,后分类load
(分类之间,看编译的顺序)