上一章应用加载流程探索我们已经了解了,程序启动加载的流程。编译过程:预编译->编译->汇编->链接->可执行mach-o。然后链接过程用到了dyld动态链接器。主要靠dyld来链接管理,了解了dyld的加载动态库流程。
9大步骤:
1.环境变量配置(环境,平台,版本,路径,主机信息)
2.共享缓存(mapSharedCache):(UIKit、CoreFoundation等)
3.主程序的初始化(instantiateFromLoadedImage)
4.加入动态库(loadInsertedDylib)
5.link主程序
6.link动态库
7.weakBind弱引用绑定主程序
8.initializeMainExecutable初始化
9.notifyMonitoringDyldMain通知dyld可以进行main()
深入分析了initializeMainExecutable 初始化流程,引入了今天研究的核心mapImage和loadImage。 在程序动态连接器初始化动态库中mapImage和loadImage的作用。
一. _objc_init初始化流程
1、环境变量的初始化
废话不多说,直接上代码:
void environ_init(void)
{
....省略
if (0 != strncmp(*p, "OBJC_", 5)) continue;
if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
PrintHelp = true;
continue;
}
if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
PrintOptions = true;
continue;
}
if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) {
SetPageCountWarning(*p + 22);
continue;
}
....省略
}
代码对应xcode的设置
只保留:
运行代码看控制台打印:
我们就可以通过
OBJC_DISABLE_NONPOINTER_ISA来设置xcode对isa的指针优化开关。
验证打印:
isa指针优化关闭,然后我们设置
OBJC_PRINT_IMAGES=YES我们打印看看:
打印了加载的动态库信息。
2、_objc_init解析
在objc4-838.1中添加代码:
@interface NYPerson : NSObject
@end
@implementation NYPerson
__attribute__((constructor)) static void function1(){
printf("function1 \n");
}
+(void)load
{
NSLog(@"%s",__func__);
}
@end
//打印结果
**function**
**2022-05-22 12:24:43.678216+0800 SXObjcDebug[13073:387196] +[NYPerson load]**
**function1**
**2022-05-22 12:24:43.680619+0800 SXObjcDebug[13073:387196] Hello World!**
**Program ended with exit code: 0**
从打印结果得知,oc的static_init()中C++析构函数function在load之前执行。我们自己定义的C++析构函数function1在load之后执行。
由上图可知,
_objc_init初始化配置了环境变量,创建线程的析构函数,初始化static静态数据,
分类表初始化,类表初始化,异常处理,缓存等操作。后面我们重点研究map_images 和 load_images具体做了哪些处理。
3、load_images解析
来来来我们直接看代码:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// 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
// 查找 load 方法
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
//调用 +load 方法 ,包括类,和分类的load
call_load_methods();
}
我们进入prepare_load_methods看看具体实现:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//非懒加载类,重写了load方法的类
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));//递归查找
}
//非懒加载的分类
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
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, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);//添加到分类表中
}
}
//进入add_category_to_loadable_list方法
void add_category_to_loadable_list(Category cat)
{
IMP method;
.........省略............
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
在进入schedule_class_load看代码:
static void schedule_class_load(Class cls)
{
//不需要调用 super load --父类的load 优先添加到表中 --子类
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->getSuperclass());//在父类中找load
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
//在进入add_class_to_loadable_list方法
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
.........省略............
//添加cls,method到表中
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
小结:load_images解析
1.当⽗类和⼦类都实现load函数时,⽗类的load⽅法执⾏顺序要优先于⼦类
2.当⼀个类未实现load⽅法时,不会调⽤⽗类load⽅法
3.类中的load⽅法执⾏顺序要优先于分类(Category)
4.load⽅法使⽤了锁,所以是线程安全的。
5.当有多个类别(Category)都实现了load⽅法,这⼏个load⽅法都会执⾏,但执⾏顺序不确定(其执⾏
顺序与类别在Compile Sources中出现的顺序⼀致)
6.当然当有多个不同的类的时候,每个类load 执⾏顺序与其在Compile Sources出现的顺序⼀致
4、read_images解析
我们在objc4-838.1中找到对应代码map_images
进入
map_images_nolock代码:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
if (firstTime) {
preopt_init();//共享缓存的优化处理
}
if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}
.........省略............
// 统计所有的类
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
.........省略............
}
if (firstTime) {
sel_init(selrefCount);//c++的构造函数析构方法初始化
arr_init();//初始化自动释放池
.........省略............
}
.........省略............
if (hCount > 0) {
//研究的重点
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// Call image load funcs after everything is set up.
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
}
sel_init的主要作用:c++的构造函数析构方法初始化
arr_init的主要作用:初始化自动释放池,散列表,关联对象初始化。
我们进入
_read_images代码中:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
bool launchTime = NO;
TimeLogger ts(PrintImageTimes);
runtimeLock.assertLocked();
.........省略............
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;//指针优化
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break;
}
}
.........省略............
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
.........省略............
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;//rebase //修护内存指针
}
}
}
}
//ASLR 虚拟内存 的binding过程
ts.log("IMAGE TIMES: fix up selector references");
.........省略............
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
//将所有`Protocol`都添加到`protocol_map`表中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
.........省略............
}
.........省略............
//初始化所有⾮懒加载的类,进⾏`rw、ro`等操作
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);//递归调用加入表中
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
}
realizeClassWithoutSwift(cls, nil);
}
}
.........省略............
}
进入NXCreateMapTable看看做了什么?
创建了
gdb_objc_realized_classes 这张表。
主要看readClass:方法的作用:
我们在
readClass中添加调试代码:
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
const char *personName = "NYPerson";
auto ny_ro = (const class_ro_t *)cls->data();
auto ny_isMeta = ny_ro->flags & RO_META;
if(strcasecmp(mangledName, personName)==0 && !ny_isMeta) //过滤元类
{
printf("NYPerson");
}
.........省略............
}
进行断点调试,研究readClass都做了什么?
通过断点我们发现并没有进行
or,rw等操作。
添加类到表中,并返回这个class.
小结:
1: 加载所有类到类的gdb_objc_realized_classes表中。
2: 对所有类做重映射。
3: 将所有SEL都注册到namedSelectors表中。
4: 修复函数指针遗留。
5: 将所有Protocol都添加到protocol_map表中。
6: 对所有Protocol做重映射。
7: 初始化所有⾮懒加载的类,进⾏rw、ro等操作。
8:遍历已标记的懒加载的类,并做初始化操作。
9:处理所有Category,包括Class和Meta Class。
10:初始化所有未初始化的类。
5、非懒加载类的加载
用上面同样的方法进行断点调试:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
.........省略............
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
const char *mangledName = cls->nonlazyMangledName();
const char *personName = "NYPerson";
auto ny_ro = (const class_ro_t *)cls->data();
auto ny_isMeta = ny_ro->flags & RO_META;
if(strcasecmp(mangledName, personName)==0 && !ny_isMeta)
{
printf("NYPerson");
}
.........省略............
//给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
.........省略............
//递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
.........省略............
//设置⽗类,isa指针的初始化
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
}
添加NYPerson代码:
@interface NYPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *gogo;
- (void)test1;
- (void)test2;
- (void)test3;
@end
@implementation NYPerson
__attribute__((constructor)) static void function1(){
printf("function1 \n");
}
+(void)load
{
NSLog(@"%s",__func__);
}
- (void)test1{}
- (void)test2{}
- (void)test3{}
@end
打印结果:
打印中发现realizeClassWithoutSwift中对类进行了
or rw操作。
小结:
realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。
6、总结
1.环境变量的初始化:通过xcode设置或代码设置OBJC_DEBUG_POOL_DEPTH等设置,可以开启对应log打印。
2._objc_init解析:
environ_init():环境变量的初始化tls_init:创建线程的析构函数static_init:运⾏C++静态构造函数runtime_init:分类表初始化,类表初始化cache_t::init():缓存的初始化_imp_implementationWithBlock_init:关于macOS的相关操作。didCallDyldNotifyRegister:标识对_dyld_objc_notify_register的调⽤已完成。
3.load_images解析:
- 1.当⽗类和⼦类都实现load函数时,⽗类的load⽅法执⾏顺序要
优先于⼦类 - 当⼀个类未实现load⽅法时,不会调⽤⽗类load⽅法
- 类中的load⽅法执⾏顺序要优先于分类(
Category) - load⽅法使⽤了锁,所以是线程安全的。
- 当有多个类别(Category)都实现了load⽅法,这⼏个load⽅法都会执⾏,但执⾏顺序不确定(其执⾏顺序与类别在Compile Sources中出现的顺序⼀致)
- 当然当有多个不同的类的时候,每个类load 执⾏顺序与其在Compile Sources出现的顺序⼀致
4.
read_images解析: - 加载所有类到类的
gdb_objc_realized_classes表中。 - 对所有类做重映射。
- 将所有SEL都注册到
namedSelectors表中。 - 修复函数指针遗留。
- 将所有Protocol都添加到
protocol_map表中。 - 对所有
Protocol做重映射。 - 初始化所有⾮懒加载的类,进⾏
rw、ro等操作。 - 遍历已标记的
懒加载的类,并做初始化操作。 - 处理所有
Category,包括Class和Meta Class。 - 初始化所有未初始化的类。
5.非懒加载类的加载:
realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。