在上两章节,我们已经了解了一个App从编译到main函数的调用,发生了什么事情,并且我们知道了_objc_init在加载镜像文件时,会在dyld动态链接器中去注册,两者之前通过此来进行通讯。但是dyld加载相关镜像文件后,这些镜像文件是如何加载到内存当中的,是以什么方式存在于内存当中的,这就是本章探究的核心。
相关文章传送门:
我们知道dyld的主体流程就是链接动态库和镜像文件,那么objc的镜像文件本身是如何进行读取到内存中的,我们从源码来解读
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
通过源码,我们基本可以得出以下结论:
_objc_init
由libSystem
库调用- 在此方法里进行自举镜像加载到内存
下面我们逐步进行分析,objc相关类等,是如何加载的
准备工作
在镜像加载之前,objc
进行了一系列的准备工作,我们来逐步分析,如下图:
environ_init
根据字面意思我们可以得出,这个方法是读取影响运行时的环境变量,可以使用 export OBJC_HELP=1
来打印环境变量,从而进行一些调试,可以再Xcode中进行设置,从而达到想要的效果打印。相关可以参考OBJC_HELP
OBJC_DISABLE_NONPOINTER_ISA
这个可以设置non-pointer
的ISA,ISA的值不需要和mask
进行与操作,直接指向OBJC_PRINT_LOAD_METHODS
这个可以打印类和分类的load
方法,对我们进行启动优化很有帮助。
tls_init
这个函数是关于线程Key的绑定,比如线程数据的析构函数
static_init
根据注释可以得出,这个函数主要做了如下事情
- 运行C++静态构造函数
- 在dyld调用我们静态构造函数之前,
libc
会调用_objc_init()
,所以必须在此前调用
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
lock_init
是一个空实现,说明objc采用的是C++的加锁机制
exception_init
初始化 libobjc的异常处理系统,用来监控崩溃等,比如未实现的方法
_dyld_objc_notify_register
通过上一章,我们对这个方法已经有了了解,这是一个dyld的注册回调函数,从而让dyld可以链接加载镜像
- 这个函数只在运行时提供给objc使用
- 注册处理程序,以便在映射和取消映射和初始化objc镜像是调用
- dyld将使用包含
objc_image_info
的镜像文件的数组,回调给mapped
函数
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
map_images
这个方法是将镜像加载到内存时候,触发的主要方法,所以我们主要来探究这个方法是怎样将数据,类,分类,方法等以什么方式加载到内存中的。
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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
的源码我们可以发现,如果hCount
表示镜像文件的个数,则调用_read_images
函数来进行加载镜像文件。所以加载内存肯定在此
_read_images
解析
由于代码比较长,我们先做一个大体的概括,然后逐步进行研究,基本处理如下:
-
第一次加载所有类到表中
gdb_objc_realized_classes
为所有类的表-包括实现和未实现的allocatedClasses
包含使用objc_allocateClassPair
分配的所有类(元类)的表 -
对所有的类做重映射
-
将所有的
SEL
注册到namedSelector
表中 -
修复旧的
objc_msgSend_fixup
调用导致一些消息没有处理 -
将所有的
Protocol
都添加到protocol_map
表中 -
对所有的
Protocol
做重映射,获取到引用 -
初始化所有非懒加载的类,进行
rw
、ro
等操作 -
遍历已经标记的懒加载的类,并做相应的初始化
-
处理所有的
Category
,包括类和元类 -
初始化所有未初始化的类
下面我们主要对类的加载来进行重点的分析
doneOnce
变量doneOnce
表示这个操作只进行一次,因为是创建表的操作,所以只需要一次创建即可,主要的代码如下
if (!doneOnce) {
doneOnce = YES;
...
initializeTaggedPointerObfuscator();
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
//✅实例化存储类的哈希表,并且根据当前类数量做动态扩容
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//✅创建一张包含所有的类和元类的表
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
//✅创建只包含已经初始化的类的表
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
ts.log("IMAGE TIMES: first time tasks");
}
类的重映射
类的重映射相关代码如下。主要是从列表中遍历出类,并进行处理和添加到相对应的表中
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for (EACH_HEADER) {
// ✅从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
for (i = 0; i < count; i++) {
// ✅数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及自己创建的类
// ✅这时候的类只有相对应的地址,无其他信息
Class cls = (Class)classlist[i];
// ✅通过readClass函数获取处理后的新类,下面具体分析readClass
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
的rw
等进行了处理,我们知道rw
里存了类的方法等,所以是不是在这里处理的呢?
但是我们在方法里打断点,发现并没有执行,说明我们创建的类和系统方法的类都没有走这个方法,所以类的rw
数据填充并不是在此
通过红框中的判断,我们可得,这个判断条件是处理专门针对未来的待处理的类的特殊操作
那么继续向下可以看到如下代码,可以看到主要是执行了addNamedClass
和addClassTableEntry
两个函数
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert does not work because of duplicates
// assert(cls == getClass(name));
assert(getClassExceptSomeSwift(mangledName));
} else {
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
}
查看addNamedClass
相关代码,主要将类添加到底层总的哈希表中
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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 {
//✅ 将类添加到总表中
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
相关代码,因为当前类已经有了地址,进行了初始化,所以也要添加到allocatedClasses
哈希表中
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
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.
assert(!NXHashMember(allocatedClasses, cls));
if (!isKnownClass(cls))
NXHashInsert(allocatedClasses, cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
至此,初始化类已经添加到两张表中了
SEL
添加到表中
SEL
相关代码的处理如下,主要也是一个表写入的操作,写入了namedSelector
表中,和类并不是一张表
//✅ 将所有SEL都注册到哈希表中,是另外一张哈希表
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->isPreoptimized()) 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的操作
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}
将所有的Protocol
都添加到protocol_map
表中
相关代码如下。
// Discover protocols. Fix up protocol refs.
//✅ 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
//✅ cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
//✅ 获取protocol哈希表
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
//✅ 从编译器中读取并初始化Protocol
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
初始化所有非懒加载的类,进行rw、ro等操作
一个类的加载分为懒加载类和非懒加载类
两者之前的主要区分是 是否实现了load
方法
-
如果实现了
load
方法,则为非懒加载类 -
如果未实现,则为懒加载类
非懒加载类加载流程
由于非懒加载类实现了load
方法,且dyld在进行镜像链接加载的时候,会调用各类的load
方法,所以必须在镜像加入内存的时候,就做好对相关类的加载
// Realize non-lazy classes (for +load methods and static instances)
//✅ 实现非懒加载的类,对于load方法和静态实例变量
for (EACH_HEADER) {
//✅ 获取到非懒加载类的列表
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
//✅ 从镜像列表中映射出来
Class cls = remapClass(classlist[i]);
// printf("non-lazy Class:%s\n",cls->mangledName());
if (!cls) continue;
// hack for class __ARCLite__, which did not get this above
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif
//✅ 再次插入到表allocatedClasses表中
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());
}
// fixme also disallow relocatable classes
// We can not disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
//✅ 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClassWithoutSwift(cls);
}
}
查看realizeClassWithoutSwift
相关代码
ro
表示readonly,是在编译时刻就已经赋值的,但是此时rw
还并没有赋值,所以这一步,主要是初始化rw
// fixme verify class is not in an un-dlopened part of the shared cache?
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
通过下面的代码我们可以发现,此时对类的superclass
和isa
进行了处理,会根据类的继承链关系进行递归操作,知道类为nil,也就是NSObject
的父类。从而完成对类的继承链路的完善
if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
cls->superclass = supercls;
cls->initClassIsa(metacls);
但是isa
找到根元类之后,根元类的isa
是指向自己的,必然不给nil
,如果不处理,就会形成无限递归,通过查看remapClass
源码,我们可以知道,remapClass
主要是对类在表中进行查找的操作,如果表中已有该类,则返回一个空值,如果没有,则返回当前类,这样保证了类只加载一次,并结束递归
/***********************************************************************
* remapClass
* Returns the live class pointer for cls, which may be pointing to
* a class struct that has been reallocated.
* Returns nil if cls is ignored because of weak linking.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class remapClass(Class cls)
{
runtimeLock.assertLocked();
Class c2;
if (!cls) return nil;
NXMapTable *map = remappedClasses(NO);
if (!map || NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {
return cls;
} else {
return c2;
}
}
最后,我们发现函数最后调用了methodizeClass
方法,根据明明,猜测是方法等的初始化
static Class realizeClassWithoutSwift(Class cls)
{
...
// Attach categories
methodizeClass(cls);
return cls;
}
在methodizeClass
函数的实现中,我们发现了,刚刚初始化的rw
的赋值,是从ro
中取出相关数据,直接赋值给rw
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
那么attachLists
是如何插入数据的呢
根据代码我们可以发现,主要通过把oldList
向后偏移addedCount
的位置,然后把新的addedLists
整体插入到表的前面,从而实现分类的方法覆盖本类同名方法,所以分类的方法会比原方法先调用,并没有覆盖
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;//10
uint32_t newCount = oldCount + addedCount;//4
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;// 10+4
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
一个非懒加载类的加载的主体流程如下
read_images
内部先创建一个全局类的表gdb_objc_realized_classes
和一个已经初始化的类的表allocatedClasses
,之后对类进行初始化,并加载到表中,然后把SEL
和Protocol
等也映射到内存对应的表中中去,和类并不是一个表,并在对非懒加载类进行处理的时候,通过realizeClassWithoutSwift
对ro
进行赋值,并且初始化rw
,之后通过methodizeClass
对rw
赋值,完成数据的加载
懒加载类加载流程
由于懒加载类并没有实现load
方法,所以不需要在启动的时候就加载到内存中,那么懒加载类是什么时候加载到内存中的呢?
通过上面非懒加载类的探究,我们知道,非懒加载类会寻找父类,并进行加载,所以如果一个懒加载类是父类,则一定会在递归中被加载
另一种猜想就是,如果这个类调用了方法,进行了消息的发送,那么此时类是一定要有rw
中的相关数据的,说明此时是肯定已经加载的。我们可以根据消息转发的源码探究
寻找函数lookUpImpOrForward
,我们发现有一个!cls->isRealized()
判断,是判断类有没有加载
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
通过打断点,我们可以发现,当类之前没有调用,第一次alloc
的时候,会进入这个判断里面,继续深入,查看realizeClassMaybeSwiftMaybeRelock
方法的实现
/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class.
* Locking:
* runtimeLock must be held on entry
* runtimeLock may be dropped during execution
* ...AndUnlock function leaves runtimeLock unlocked on exit
* ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
assert(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
我们发现,这个方法主要是区分Swift,是OC时,主要运行了方法realizeClassWithoutSwift
,这个方法,在上面非懒加载类流程中讲过,主要是对类的相关属性进行初始化
至此,一个懒加载类也已经加载完成了,总结一句话就是,一个懒加载类的加载,是在第一次被调用的时候才进行加载,并不是启动的时候就加载进内存
至此,一个类所需要属性的赋值加载都已经完成
处理所有的Category
,包括类和元类
关于分类的一些基础的知识,可以参考下面的文章👇
一个分类的加载和类的加载类似,也是需要区分load
方法的调用
- 懒加载的 - 编译时就确定
- 非懒加载的 - 运行时确定
非懒加载分类
非懒加载的分类即实现了load
方法的分类,我们可以和类加载一起来进行分析
1.懒加载类
通过上面的探索,我们知道懒加载类是在第一次使用的时候就会加载到内存中,那么由于分类是一个非懒加载的分类,他也需要加入到类信息的ro
和rw
中,所以这时候也会提前加载类,而不是等到发送消息的时候才初始化,具体代码如下
// ✅ 取出非懒加载分类列表
category_t **catlist = _getObjc2CategoryList(hi, &count);
// ✅ 判断是实例方法
if (cat->instanceMethods || cat->protocols || cat->instanceProperties)
{
...
// ✅ 添加到未绑定的分类列表中
addUnattachedCategoryForClass(cat, cls, hi);
}
...
// ✅ 判断是类方法
if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties))
{
...
addUnattachedCategoryForClass(cat, cls, hi);
}
// ✅ 由于是懒加载类,此时并没有加载,所以不走此判断
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
_read_image
方法走完后,所有实现load
方法的非懒加载类和分类都已经加入到表中,但是非懒加载分类此时并没有绑定到类中,所以在后续load_image
函数,调用load
方法时,会进行准备工作,此时会加载相关类,具体代码如下
void
load_images(const char *path __unused, const struct mach_header *mh)
{
...
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
// ✅ 准备load方法的调用
prepare_load_methods((const headerType *)mh);
}
...
}
void prepare_load_methods(const headerType *mhdr)
{
...
//✅ map_images 完毕了
category_t **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");
}
// ✅ 加载对应的类和添加分类到load list
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
2.非懒加载类
非懒加载类和非懒加载分类的类比较简单,非懒加载类在_read_image
中已经插入表,并进行了加载,所以在判断cls->isRealized()
中已经进行了加载,所以直接执行判断方法remethodizeClass
我们可以发现remethodizeClass
主要执行了attachCategories
函数,将分类插入到类的rw
中
由于分类的方法是最后执行,所以方法插入时,如果有同名方法,会先找到分类的方法,但是类中本来的方法并没有被覆盖
懒加载分类
由于分类是懒加载的,所以_getObjc2CategoryList
表中并没有这个方法,所以在_read_image
方法里是没有办法检索出来的
懒加载分类是在编译的时候,直接就写入了类的ro
中,不管类是非懒加载还是懒加载,当他进行加载的时候,直接蓉ro
中取值,赋值给对应的rw
至此,相关镜像的加载已经完成
load_image
我们知道load_image
方法是用来加载类和分类的load
方法的,那么是如何调用的呢,我们还是根据源码来研究,主要发现以下两个步骤:
- 准备
load
方法调用所需要的数据 - 调用
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
// ✅准备load方法调用所需要的数据
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// ✅调用load方法
call_load_methods();
}
prepare_load_methods
这个方法是准备类和分类的load
表的方法,主要是创建了非懒加载类和非懒加载分类的列表,具体的实现如下
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// ✅遍历非懒加载类
schedule_class_load(remapClass(classlist[i]));
}
category_t **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);
assert(cls->ISA()->isRealized());
// ✅添加分类到非懒加载分类列表中
add_category_to_loadable_list(cat);
}
}
在添加非懒加载类到列表中时,会进行递归添加父类
static void schedule_class_load(Class cls)
{
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->superclass);
// ✅添加类到列表中
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
在添加到列表中时,会先申请对应的空间,可以看到列表中存储的loadable_class
的结构为16字节,所以申请现有空间2倍+16字节,并赋值对应的cls
和method
。
分类添加到列表中的逻辑和这个基本一样,只不过存储的列表和model的名字不同
struct loadable_class {
Class cls; // may be nil
IMP method;
};
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
// ✅申请空间
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
// ✅model赋值
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
call_load_methods
call_load_methods
方式是真正调用load
方法的函数,主要操作如下
- 调用时放入自动释放池
- 循环遍历列表,调用对应的
load
方法 - 调用完出栈自动释放池,保证调用一次
- 根据调用顺序可知,类的load方法比分类的load方法先调用
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
// ✅压栈自动释放池
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
// ✅调用类的load方法
call_class_loads();
}
// 2. Call category +loads ONCE
// ✅循环调用分类load方法,仅一次
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
// ✅出栈自动释放池
objc_autoreleasePoolPop(pool);
loading = NO;
}
类具体调用load
方法是在call_class_loads
中,主要的操作为遍历非懒加载类列表,直接获取到IMP
,强转为load_method_t
,并直接调用函数实现,没有走消息发送流程
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
// ✅遍历列表,得到IMP并强转
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
// ✅直接调用函数
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
分类的调用和类的调用流程基本类似,只不过是从loadable_categories
列表中获取数据,并且调用后释放,保证调用一次
static bool call_category_loads(void)
{
...
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
...
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
}
至此,类和分类的load调用也已经完成了
initialize
通过上面我们已经知道了一个类的加载和load方法的调用,但是并没有找到initialize
方法的调用,所以我们猜测是不是和懒加载类一样,会在消息发送的时候调用呢,果然我们找到了如下代码
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
...
}
// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
···
initializeNonMetaClass(nonmeta);
···
}
我们找到了initializeNonMetaClass
方法,发现方法还是老套路,递归调用父类initialize
,然后下方有一个真正的调用函数callInitialize
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
...
// ✅递归调用父类
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
...
{
// ✅调用Initialize方法
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
...
}
callInitialize
这个方法已经非常熟悉了,就是普通的消息发送
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
至此Initialize方法也调用完成
总结
本章主要讲述了objc中类,方法,协议,分类等加载到内存中的步骤和load方法的调用流程
主要的流程如下:
- 准备相关环境变量,线程,锁,异常监听和
dyld
注册等前置条件 - 通过
map_images
中的_read_image
来处理类,方法,协议,分类等 - 类的处理创建了一张总表和一张已经初始化的表来管理
- 非懒加载类在
_read_image
中处理,从ro
中读取对应数据,赋值rw
- 懒加载类要在第一次被调用的时候才做类加载的相关处理
- 非懒加载分类在运行时确定,如果没有加载类,会做响应操作
- 懒加载分类在编译时确定,直接在
ro
中赋值给类 load_image
主要调用类和分类的load
方法,并储存在不同的列表中- 根据调用顺序,类的
load
比分类的先调用。其他方法,由于分类后插入rw
中,所以分类比类的方法先调用,但是并没有与覆盖 initialize
在消息发送时调用