前言
在上一篇中我们了解了应用程序的加载流程,iOS通过dyld
把相应的文件加载到内存中,然后调用main
函数启动App
,主要研究了在main
函数之前那段时间的流程,而这篇我们再研究一下关于类的加载
,类
是如何加载到内存的?以及相关的rw
、ro
是在什么时候写入的?
源码分析
要分析类的加载,我们还是从源码入手,根据源码的执行流程慢慢去深入、分析各个阶段都做了什么?首先从App
启动的_objc_init
方法开始
_objc_init
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();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
_objc_init
函数主要做了以下事情:
- 环境变量初始化
- 关于线程key的绑定
- 运行C++静态构造函数
- Runtime运行时环境初始化
- 初始化libobjc的异常处理系统
- 缓存条件初始化
- 启动回调机制
- 镜像文件加载
下面重点分析一下_dyld_objc_notify_register
方法,这里就是关于类
的加载流程。
_dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
上面的方法包含3个方法的调用,map_images
,load_images
和unmap_image
,先来看一下map_images
。
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
调用了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
通过读取_read_images
方法,主要包含了以下10个流程:
- 条件控制进行一次的加载
- 修复预编译阶段
@selector
的混乱问题 - 错误混乱的类处理
- 修复重映射一些没有被镜像文件加载进来的类
- 修复一些消息
- 当类里面有协议的时候读取协议
- 修复没有被加载的协议
- 分类处理
- 类的加载处理
- 没有被处理的类,优化那些被侵犯的类
void _read_images(header_info **hList, uint32_t hCount,
int totalClasses, int unoptimizedTotalClasses)
{
...... // 省略部分代码
if (!doneOnce) {
......
// 表的创建,开辟的总容积 = 类的大小 * 4/3,4/3:负载因子,之前在cache_t分析中是3/4的逆运算
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// 总表,所有类的表
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
......
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);
......
}
}
...... // 省略部分代码
}
通过_read_images
上面的流程,我们最终是研究加载类的信息,从上面获取到类的列表,开始读取类的信息,再看一下readClass
的函数定义
readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
......
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) {
if (Class newCls = popFutureNamedClass(mangledName)) {
......
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
}
if (headerIsPreoptimized && !replacing) {
......
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
// 把类名和类的地址进行关联
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
......
}
addClassTableEntry(cls);
}
......
return cls;
}
从readClass
的代码中我们看到了类
读取ro
,对rw
赋值的相关操作,但实际是否按代码这样操作呢?接下来通过LLDB
调试的方式来验证一下,首先需要在readClass
中加上一些判断代码,要确认进来的类是我们研究的对象,这里以LGPerson
为例。
const char *mangledName = cls->nonlazyMangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
// 普通写得类 定位分析打印
printf("%s - Atom: 要研究的: - %s\n",__func__,mangledName);
}
在mangledName
下增加上面代码,然后打个断点开始调试。
根据上面的运行结果可以得知,
ro
和rw
不在readClass
这里实现,这里只做表的加入。因而readClass
主要做了如下操作:
- 读取
class
总表和mach-o
的class
表进行比对 - 把类名和类的地址进行关联
从这里看还不是类操作
rw
和ro
的流程,接着我们还是从LLDB
调试的方式去跟踪相关的流程,把上面的研究对象代码继续放在上面_readImage
方法的相关类加载的函数里,并在自定义打印代码处打上断点。
void _read_images(header_info **hList, uint32_t hCount,
int totalClasses, int unoptimizedTotalClasses)
{
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// ----------- 自定义类 开始 --------------
const char *mangledName = cls->nonlazyMangledName();
const char *LGPersonName = "LGPerson";
// 节约内存 速度
if (strcmp(mangledName, LGPersonName) == 0) {
// 普通写得类 定位分析打印
printf("%s Realize non-lazy classes -Atom: 要研究的: - %s\n",__func__,mangledName);
}
// ----------- 自定义类 结束 --------------
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't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
// ----------- 自定义类 开始 --------------
const char *mangledName = cls->nonlazyMangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
// 普通写得类 定位分析打印
printf("%s -resolvedFutureClasses-Atom: 要研究的: - %s\n",__func__,mangledName);
}
// ----------- 自定义类 结束 --------------
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
}
再次运行,发现当前运行并没有断住,难道类的加载过程不走这里?根据疑问我们发现上面的一段注释。
// Realize non-lazy classes (for +load methods and static instances)
非懒加载的类,也就是执行了+load
方法会走这里,接着我们在自定义的LGPerson
类里加上load
方法,再次运行。
这次断点来到了我们判断条件里,一步步往下走,走到了
realizeClassWithoutSwift
函数,这就是我们接下来的研究重点。
realizeClassWithoutSwift
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
......
// 从mach-o读取ro数据
auto ro = (const class_ro_t *)cls->data();
// 获取元类
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 这里从ro里复制一份到rw,class设置rw
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
......
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// 设置类的继承关系和初始化ISA
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
......
// Attach categories
methodizeClass(cls, previously);
return cls;
}
realizeClassWithoutSwift
主要做了以下几点:
- 从
mach-o
读取ro数据 - 把
ro
的数据复制一份给rw
- 设置
class
的继承链 - 初始化
ISA
走位 - 调用类的方法写入函数
根据
realizeClassWithoutSwift
的对rw
和ro
操作的代码,我们实际项目运行验证一下。通过
LLDB
调试在这里可以打印出ro
和rw
信息,验证了在realizeClassWithoutSwift
方法里对ro
和rw
的读取和赋值,在当前的函数的最下面我们可以看到调用了methodizeClass
函数,接下来再看一下这个函数的源码。
methodizeClass
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
......
// Install methods and properties that the class implements itself.
// 获取类的方法列表
method_list_t *list = ro->baseMethods();
if (list) {
// 对方法名写入到methodLists,并进行排序
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
......
}
methodizeClass
主要包含以下几点:
- 读取类的方法列表名
- 对方法名写入到methodLists并进行排序
- 获取rwe
- 获取协议列表
- 加载分类
上面
methodizeClass
调用了prepareMethodLists
,以下是prepareMethodLists
函数的实现代码。
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
......
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
// 对方法列表进行排序
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
......
}
fixupMethodList
方法的源码实现
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
我们在fixupMethodList
方法里插入一段打印的代码,然后运行看看自定义的类的信息。
这里看到在自定义的类
LGPerson
里经过fixupMethodList
方法执行后,得到地址排好序的方法列表。
总结
根据上面的源码和运行结果分析,类的加载流程大致有下面几个:
- 通过调用
readImages
读取类的镜像文件 readClass
读取类的名字,并和类进行关联realizeClassWithoutSwift
处理superclass
、isa
,读取ro
数据,并给rw
赋值methodizeClass
获取到ro
里的methodlist
,写入方法名并给方法列表排序
写到最后
在本篇中也提到了懒加载
和非懒加载
、rwe
,以及在methodizeClass
方法里对分类的加载,由于篇幅的原因,就放在后面的篇章中解析。