app的加载分析
应用程序的加载需要多个底层库的依赖
库就是可执行的代码的二进制 — 被操作系统写入内存
常见的库文件类型:静态库 .a .lib — 动态库:framework .so .dll
动静态库 加载区别:
静态库在链接阶段,会将汇编生成的目标与引用的库一起链接打包到可执行文件中。
动态库在程序编译时不会链接到目标代码中,而是在程序运行时才被载入。减小打包app大小,共享内容节约资源,动态更新(仅限官方)
比如 UIKit、libdispatch、libobjc.dyld.
编译的过程

加载过程
app启动后由动态链接器:dyld来链接(链接的也是可执行二进制文件)。

- app启动。
- 加载libsystem 。
- Runtime向dyld注册回调函数。
- 加载新image
- 执行 map_images、load_images
- 调用main函数
在main()函数之前的事情 __前缀代表汇编
dyld -> libsystem init -> libdispatch -> objc init -> _dyld_objc_notify_register
void _objc_init(void)中会做一些初始化比如环境变量 异常函数注册
其中 _dyld_objc_notify_register(&map_images, load_images, unmap_image);注册了回调函数,拿到dyld加载的相关数据。
map_images ()内部依次map_images_nolock ()-->_read_images ()
map_images () -->_read_images ()
map_images中主要干了这些事:
- 类的初始化 realizeClass 设置rw、ro
- 分类的处理 分类的方法、协议、属性添加到类中
- 加载相应的哈希表
void _read_images {
if (!doneOnce) {
doneOnce = YES;
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);
}
}
}
1.类的处理
readclass() 做了2个重要操作 addNamedClass()和addClassTableEntrt() 前者把类插入一张objc_realized_classes表后者把类插入到allocatedClasses表中。2.方法编号处理
5.非懒加载类处理
realizedClassWithoutSwift() 读取class的data() rw创建 rw->ro赋值
父类和元类的实现 归属关系确定(父类指向等)
方法属性以及分类的处理:attachmethod() 这个函数里面会读取ro的信息对rw的具体数据进行赋值method_list_t、property_list_t、protocol_list_t加载分类信息attachCategories()
那么懒加载类呢? 在第一次发送消息的时候会判断isRealized()这时候才会去实现类。
如果子类是非懒加载类 那么父类也会被实现- 分类的处理
要注意的是 懒加载的分类方法是被编译期就写入到ro中
那么非懒加载分类情况下:非懒加载类走正常流程把非懒加载类的信息添加到rw中。而懒加载的类则会被提前实现并把非懒加载类的信息添加到rw中。
关于类的拓展:
在编译的时候直接作为类的一部分 那么也就是说你没有.m文件那就不能添加类拓展
- 分类的处理
-
Load_Images()
前面讲过在_objc_init()中map_images()用于处理类相关的事情 。接着分析 load_images()
断点停在load_images()入口时 类的load方法还没有开始调用
看源码
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
//准备非懒加载类load方法,将其放在一个数组中
prepare_load_methods((const headerType *)mh);
}
//遍历加载load方法
call_load_methods();
}
非懒加载的类才有load方法的实现 这里用的一个局部代码块处理
prepare_load_methods((const headerType *)mh);
内部先查找类的load方法(递归找了父类的load实现)
然后add_class_to_loadable_list(cls)放在一个数组中
同时cls->setInfo(RW_LOADED)
查找分类中的load 然后add_categroy_to_loadable_list(cls)
大致处理同上
提醒一下 调用add_categroy_to_loadable_list(cls)之前 会执行一次
realizeClassWithoutSwift(cls)这也是分类在实现了load之后会导致相关的类会提前实现的一个原因call_load_methods();
整理一下 在load_images()中会先把实现了load()的类和分类取出来放在数组中 然后分别循环从数组中取出类和分类中的load方法调用方法
注意 如果主类和分类都有 会先调用主类 然后调用分类的load()方法 并且 这里的调用是直接调用函数 不是走的msgsend 所以不存在方法覆盖的问题 都会调用