类的加载原理:
iOS 类的加载原理上
iOS 类的加载原理中
iOS 类的加载原理下
分类的加载原理补充及类扩展 , 关联对象介绍
我们在 iOS 应用程序加载流程分析 中介绍了 dyld
,其中 dyld
在加载的过程中会做一件重要的事情就是链接镜像文件 images
,但是这里只是映射过来,还只是一个库,还没有变为我们的内存。例如创建的一个 LGPerson
的类,里面有方法跟协议,但是只要加载到内存的时候,我们才能初始化它,并调用对应的方法跟协议,才能使用它。那么这里我们就要讲一个很重要的过程,就是类如何加载到内存中。镜像文件是一种 MachO
格式,也就是读取 MachO
文件并获取到类及相关方法对应的地址信息,并加载的内存中的过程。
_objc_init 分析
void _objc_init(void)
{
// 相关变量的一些赋值
static bool initialized = false;
if (initialized) return;
initialized = true;
// 环境变量的初始化
environ_init();
// 线程暂存缓存池的创建
tls_init();
// 全局静态 c++ 函数调用
static_init();
// unattachedCategories.init 跟 allocatedClasses 这两张表的初始化
runtime_init();
// 异常的监听
exception_init();
#if __OBJC2__
// 缓存条件初始化
cache_t::init();
#endif
// 启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
// map_images()
// load_images()
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
讲类的加载我们要从 map_images
开始讲起,但是 map_images
是被 _dyld_objc_notify_register
函数调起的,所以讲 map_images
之前先看下 在 _dyld_objc_notify_register
之前 _objc_init
方法中又做了哪些事情。下面我们来介绍一下。
environ_init
环境变量的初始化
environ_init
方法主要做了环境变量的初始化工作,这里我们需要关注的就是红色划线的这部分代码,我们主要是在调试跟打印的时候会用到。这里会打印出环境变量相关的信息。
tls_init
线程暂存缓存池的创建
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
static_init
全局静态 c++ 函数调用
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
这里需要注意的一点是,在我们前面也讲了,在 dyld
源码的 doModInitFunctions
方法中会自动调用全局的 c++
静态函数。但是在 objc
源码里面这里比较特殊,objc
中的 c++
静态函数不需要由 dyld
来调用,都是由 objc
自己负责调用。这主要是方便 objc
中所以的环境能及时的准备充分得当。这里已经开启了 runtime
的下层,所以写在 objc
中的 c++
静态函要保证立即调用。
runtime_init
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
unattachedCategories.init
跟 allocatedClasses
这两张表的初始化。
exception_init
异常的监听
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
cache_t::init()
缓存条件初始化。
_imp_implementationWithBlock_init
启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。
read_images
主体流程
在以上事件完成以后会执行 _dyld_objc_notify_register(&map_images, load_images, unmap_image)
,这里我们先来看一下 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
函数中这里就两行代码,紧接着会执行 map_images_nolock
函数。
map_images_nolock
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
map_images_nolock
函数中我们只看跟镜像文件加载相关的代码。这里就来到了我们要探究的比较重要的一个函数 _read_images
。
_read_images
在这里我们可以看到,_read_images
函数中有三百多行代码,但是当我们把每段代码都折行收缩的后,可以看到每段代码折行都对应一个 ts.log
,每个 ts.log
都对应不同的功能。总结了一下 _read_images
主要做了以下事情。
- 1: 条件控制进行一次的加载
- 2: 修复预编译阶段的
@selector
的混乱问题- 3: 错误混乱的类处理
- 4:修复重映射一些没有被镜像文件加载进来的 类
- 5: 修复一些消息!
- 6: 当我们类里面有协议的时候 : readProtocol
- 7: 修复没有被加载的协议
- 8: 分类处理
- 9: 类的加载处理
- 10 : 没有被处理的类 优化那些被侵犯的类
这里我们对着代码看下其中几条。
1: 条件控制进行一次的加载
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// 表的创建,gdb_objc_realized_classes 是一张总表,不管类是否实现都在这张表中,前面讲的 runtime_init 中讲的 allocatedClasses 表中存放的是已经开辟过的类
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
2: 修复预编译阶段的 @selector 的混乱问题
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;
}
}
}
}
这里因为 SEL sel = sel_registerNameNoLock(name, isBundle)
是从当前文件中读取的,是通过 dyld
读取的。而 SEL *sels = _getObjc2SelectorRefs(hi, &count);
是从 MachO
文件中读取的,是虚拟地址,是变化的。所以要以 sel
为准。因为 SEL
除了函数名称以外还有函数地址。所以这里判断相同函数名的 sel
与 sels[i]
是否相等。如果不相等就进行修改 sels[i] = sel
。
3: 错误混乱的类处理
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
这里我们分别在 readClass(cls, headerIsBundle, headerIsPreoptimized)
前后 po
cls
,可以发现在 readClass(cls, headerIsBundle, headerIsPreoptimized)
前 cls
只是从 MachO
文件中读取的地址,但是执行完 readClass
之后,cls
就被关联上了类名。那么 readClass
具体做了什么呢?这里我们来具体看一下。
readClass
分析
在
readClass
函数的实现中我们可以看到,cls
我们刚传进来的时候是地址,但是返回出去之后会被关联类名,那么是哪一步做的关联呢,这里我们来断点看一下。
首先我们打印
mangledName
可以看到,会打印所有类的名称。在 LGPerson
之前都是系统的类,这里做一下判断,然后断点看一下。
步骤 1
步骤 2
步骤 3
通过断点我们可以看到,readClass
会在步骤 1 跟步骤 2 执行 addNamedClass
跟 addClassTableEntry
,下面会有这两个方法的介绍。函数会在步骤 3 返回 cls
。
addNamedClass
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
// 这里会把 name 添加到 NXMapInsert 表中
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
// 判断当前类是否被知道,如果不被知道就把 cls 添加到要加载的表中
if (!isKnownClass(cls))
set.insert(cls);
// 判断是否需要添加元类,如果需要的话递归调用 addClassTableEntry 方法,类的加载要同步把元类也要加载进来
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
网友评论