dyld 和Objc 的关联
一 ,引言
前边我们已经学习了iOS开发过程中的相关程序启动的重要角色dyld
,通过dyld
帮助我们做了很多准备的工作,加载相关的类,初始化相关environ_init
环境变量初始化。runtime_init
和相关的cache_init
等等操作,为我们程序启动做了很多的准备工作。是我们程序启动必不可少的一部分
进入objc_init
的源码中我们截取片段来解释
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
1.1 环境变量初始化(environ_init)
进入环境变量初始化过程中,我们可以学习如何控制环境变量来打印相关的日志。在控制台显示出来。其中控制打印日志的代码如下
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
_objc_inform("%s: %s", opt->env, opt->help);
_objc_inform("%s is set", opt->env);
}
打印的日志如下;
objc[11460]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[11460]: OBJC_PRINT_IMAGES is set
objc[11460]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[11460]: OBJC_PRINT_IMAGE_TIMES is set
objc[11460]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[11460]: OBJC_PRINT_LOAD_METHODS is set
objc[11460]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[11460]: OBJC_PRINT_INITIALIZE_METHODS is set
objc[11460]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[11460]: OBJC_PRINT_RESOLVED_METHODS is set
objc[11460]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[11460]: OBJC_PRINT_CLASS_SETUP is set
其中我们针对其中的某一个环境进行设置,再次打印结果,例如我们设置打印所有加载的文件的相关的load
方法,设置环境OBJC_PRINT_LOAD_METHODS = YES
然后再次打印,控制台日志如下:
objc[11313]: LOAD: class 'NSApplication' scheduled for +load
objc[11313]: LOAD: class 'NSBinder' scheduled for +load
objc[11313]: LOAD: class 'NSColorSpaceColor' scheduled for +load
objc[11313]: LOAD: class 'NSNextStepFrame' scheduled for +load
objc[11313]: LOAD: category 'NSColor(NSUIKitSupport)' scheduled for +load
objc[11313]: LOAD: +[NSApplication load]
objc[11313]: LOAD: +[NSBinder load]
objc[11313]: LOAD: +[NSColorSpaceColor load]
objc[11313]: LOAD: +[NSNextStepFrame load]
objc[11313]: LOAD: +[NSColor(NSUIKitSupport) load]
objc[11313]: LOAD: category 'NSError(FPAdditions)' scheduled for +load
objc[11313]: LOAD: +[NSError(FPAdditions) load]
objc[11313]: LOAD: class '_DKEventQuery' scheduled for +load
objc[11313]: LOAD: +[_DKEventQuery load]
objc[11313]: LOAD: class 'LGPerson' scheduled for +load
objc[11313]: LOAD: +[LGPerson load]
2020-10-13 14:57:47.107284+0800 KCObjc[11313:152490] 0x100877620
1.2 tls_init 和 static_init
是相关的静态变量初始化,已经_objc_pthread_key的初始化
1.3 runtime_init 和 exception_init、cache_init
runtime_init
是新加入的,顾名思义是相关的runtime初始化,
exception_init
是相关的异常,初始化异常的处理操作,通常是注册一个回调函数告诉系统,如果程序运行出错,这进行相关的异常回调进行处理。
cache_init
就是初始化相关的缓存操作
1.4 _dyld_objc_notify_register
_dyld_objc_notify_register
是我们注册相关的重要函数,我们知道在dyld
的程序启动中会进行相关的闭环,而其中就有_dyld_objc_notify_register
,所以接下来就让我们来学习一下dyld
和 objc
的关联关系以及是如何通过_dyld_objc_notify_register
进行关联的。
二,dyld和objc的关联
通过文章第一部分我们能清楚的知道,在objc_init
中是通过_dyld_objc_notify_register
来注册相关的类信息,从而关联相关的类的方法
,协议
,以及属性
等;是如何进行关联的呢?让我们一探究竟。
我们看_dyld_objc_notify_register
的定义如下
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
我们通过定义清楚的知道:此处有三个参数mapped
类型为_dyld_objc_notify_mapped
,init
类型为_dyld_objc_notify_init
, unmapped
类型为_dyld_objc_notify_unmapped
,
在objc_781
开源的源码中我们只能定义进入到该方法的定义,却进入不了该方法的调用过程
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
但是在另一份开源的代码中dyld
我们能清楚的看到有相关的调用过程。代码调用如下
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
这就是说改方法是垮库
执行和运行的。那么接下来让我们详细的学习改方法是如何通过这三个参数的调用进行类的关联的。
2.1, _dyld_objc_notify_mapped(mapped)
首先进入相关的函数调用registerObjCNotifiers
中能看到相关的参数定义
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
再次进入notifyBatchPartial
的函数实现能看到相关的函数调用过程,接下来看看相关的函数调用。
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
然后进入到objc_781
环境,全局搜索相关的函数调用map_images(
,我们能进入相关的函数调用过程,
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
在此进入到map_images_nolock
中,我们能发现其中有很多加载相关类的信息;
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
我们知道,map_images
这个函数的主要功能就是为了映射相关的类信息,所以此处才是我们研究的重点,接着进入到相关的类方法的定义_read_images方法
顺着源码分析我们能得到很多关于类的信息,
- 1 : 条件控制进⾏⼀次的加载
- 2 修复预编译阶段的
@selector
的混乱问题 - 3 错误混乱的类处理
- 4 修复重映射⼀些没有被镜像⽂件加载进来的 类
- 5 修复⼀些消息!
- 6 当我们类⾥⾯有协议的时候 :
readProtocol
- 7 修复没有被加载的协议
- 8 分类处理
- 9 类的加载处理
- 10 没有被处理的类 优化那些被侵犯的类
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
进入到readClass
函数中能发现才是加载相关类的方法有
- 1 addNamedClass(cls, mangledName, replacing);
实现如下
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
如果是元类,则加入到相关的元类中,否则加入相关的类以及缓存的映射表
- 2 addClassTableEntry(cls);
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
2.2, _dyld_objc_notify_init(init)
上面我们已经知道第一步只是作为映射相关的类信息的作用,接下来我们才是研究类的信息以及方法是如何加载进入到类存的,在全局搜索load_images
进入到相关的定义
有两个版本,一个是objc-runtime-new
和objc-runtime-old
;我们研究objc-runtime-new
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
- 1 先判断相关的分类,如果该类有分类的实现,则加载分类的实现;
- 2 加载相关的方法
prepare_load_methods
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
先取出改类的所有非懒加载
类列表,再次进行遍历,
以及所有的方法,协议等加入到相关的列表;add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
在通过cls->getLoadMethod()
去取出该类下的所有方法,加入到该类的映射表;
mlist = ISA()->data()->ro()->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
if (0 == strcmp(name, "load")) {
return meth.imp;
}
}
}
2.3, _dyld_objc_notify_unmapped(unmapped)
同理再次进入相关的unmapped
调用方法,
void
unmap_image(const char *path __unused, const struct mach_header *mh)
{
recursive_mutex_locker_t lock(loadMethodLock);
mutex_locker_t lock2(runtimeLock);
unmap_image_nolock(mh);
}
起中作用就是相关的遍历,查找未被映射的类,删除它,释放表结构
for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
if (hi->mhdr() == (const headerType *)mh) {
break;
}
}
if (!hi) return;
if (PrintImages) {
_objc_inform("IMAGES: unloading image for %s%s%s\n",
hi->fname(),
hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "");
}
_unload_image(hi);
// Remove header_info from header list
removeHeader(hi);
三,总结
通过以上的三步,先将一个类映射到相关的表里,然后加载到先前创建的映射表,然后进行存储,便于以后的查找,最后再次检查是否将所有的类信息都映射到相应的表结构中,如果没有,则先将该表进行删除释放,再次进行映射,进行插入操作,这就是相关的dyld
和 objc
的关联关系,由于代码不能运行,只能靠自己的分析去解决问题,如果有什么不足请多指教