写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
- iOS 底层原理探索 之 应用程序加载原理dyld (下)
以上内容的总结专栏
细枝末节整理
前言
在前两篇文章内容中,我们探索了应用程序的加载,是通过dyld
去链接镜像文件images
,映射到我们的程序中。但是,也仅仅是做了映射的工作,并没有装载到内存中去。比如我们有一个类,类中有方法、协议等等一些内容,这些内容需要加载到内存中去,我们才可以将类实例化,并调取其方法。
我们知道链接的镜像文件是machO
格式的,那么如何通过machO
中的地址将其读取到内存中去呢?最好是有一张表能够来存储所有的类信息,然后类的初始化,其内部数据的 ro
和rw
初始化。 对与这段猜想不知是否正确,接下来我们就一步步验证,看类的加载究竟是怎样的一个流程。
_objc_init
上一篇章在加载images的时候是在 _objc_init
中调用了 _dyld_objc_notify_register
所以,我们从 _objc_init
开始,探索下在 _dyld_objc_notify_register
之前的相关 init
:
/***********************************************************************
* _objc_init
* 引导程序初始化,用dyld注册我们的镜像通知程序
* 在库初始化时间之前被libSystem调用
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// 修复延迟初始化直到找到一个使用objc的镜像?
// 环境变量的初始化
environ_init();
// 线程局部存储的初始化
tls_init();
// 运行c++静态构造函数,Libc在dyld调用静态构造函数之前调用_objc_init(), 所以我们得自己做
static_init();
// 初始化了两整表 unattachedCategories,allocatedClasses
runtime_init();
// 初始化libobjc的异常处理系统
exception_init();
#if __OBJC2__
cache_t::init();
#endif
//启动回调机制。通常这样不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载 trampolines dylib
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
- 这个函数中为什么
map_images
需要取地址,load_images
并不需要呢?
- 取地址在这里是
指针传递
,这样这里的函数能够和内部调用的函数指针同步发生变化,因为这里的map_images
很重要,它要开始映射整个镜像文件,是一个耗时的过程,如果这个过程没有发生同步变化,就会发生错乱,所以要保持地址的同步变化; - 而
load_iamges
只是调用load
方法而已。
map_images
/***********************************************************************
* map_images
* 处理dyld映射到的给定镜像
* 在使用特定于abi的锁后调用与abi无关的代码
*
* 锁定:写锁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
:
map_images_nolock
/***********************************************************************
* map_images_nolock
* 处理dyld映射到的给定镜像
* 所有的类注册和修正都被执行(或延迟等待)
* 发现丢失的超类等,并调用+load方法
*
* Info[]是自下而上的顺序,即libobjc将在早些时候
* 数组比任何链接到libobjc的库
*
* 锁定:loadMethodLock(旧的)或runtimeLock(新的)由map_images获取
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif
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;
// 如果需要,执行首次初始化
// 该函数在普通库初始化器之前调用
// 修复延迟初始化直到找到一个使用objc的镜像?
if (firstTime) { ... }
if (PrintImages) { ... }
// 找到所有带有Objective-C元数据的镜像
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) { ... }
}
// 执行必须延迟到的一次性运行时初始化
// 可执行文件本身被找到。这需要提前完成
// 进一步的初始化
// 可执行文件可能不在这个infoList中,如果
// 可执行程序不包含Objective-C代码,而是Objective-C
// 稍后动态加载
if (firstTime) { ... }
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// 在一切设置完成后调用镜像加载函数
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
首次初始化完成之后, 开始 _read_images
:
_read_images
/***********************************************************************
* _read_images
* 对链接中的头执行初始处理
* 以headerList开头的列表
*
* 称为:map_images_nolock
*
* 锁定:由map_images获取的runtimeLock
**********************************************************************/
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();
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// 1、条件控制进行一次的加载 找到一张全部的表
if (!doneOnce) { ... }
// 2、修复 @selector 引用
// sel 名字 + 地址
// 一个是machO中的地址(有相对偏移地址),一个是dyld加载的地址(需要以此为基准)需要进行修复(重定向)
static size_t UnfixedSelectors;
{ ... }
ts.log("IMAGE TIMES: fix up selector references");
// 3、发现类,修复未解决的未来类
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover classes");
// 4、修复重新映射的类
// 类列表和非惰性类列表保持未重映射
// 类引用和超级引用被重新映射以进行消息分派
if (!noClassesRemapped()) { ... }
ts.log("IMAGE TIMES: remap classes");
#if SUPPORT_FIXUP
// 5、修复旧的objc_msgSend_fixup调用站点
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
// 6、发现协议,修复协议参考
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover protocols");
// 7、修复 @protocol 引用
// 预优化图像可能有此权限
// 已经回答了,但我们不确定
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up @protocol references");
// 8、发现类别,只有在初始类别之后才这样做
// 附件已完成,对于在启动时出现的类别
// 发现被延迟到第一次load_images调用之后
// 对_dyld_objc_notify_register的调用完成
if (didInitialAttachCategories) { ... }
ts.log("IMAGE TIMES: discover categories");
// 9、类别发现必须晚,以避免潜在的竞赛
// 当其他线程之前调用新的类别代码时
// 这个线程完成它的修复
// 10、 +加载由prepare_load_methods()处理
// 实现非惰性类(用于 实现了 +load 方法的静态实例)
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: realize non-lazy classes");
// 实现新解析的未来类,以防CF操纵它们
if (resolvedFutureClasses) { ... }
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// 打印过程中统计
if (PrintPreopt) { ... }
#undef EACH_HEADER
}
到这里,其实很明显了: map_images
管理文件中和动态库中的所有符号 (class protocol selector category)。 上面的第十条就是关于类的加载。
if (!doneOnce) { ... } 条件控制进行一次的加载
// 带标记的指针混淆器旨在使其更加困难 要让攻击者构造一个特定对象作为标记指针, 在存在缓冲区溢出或其他写控制的情况下 内存。当设置时,混淆器与带标记的指针xor 或检索有效负载值。它们一开始就充满了随机性 使用
initializeTaggedPointerObfuscator();
// namedClasses
// 预优化的类不在这个表中
// 4/3是NXMapTable的负载因子
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// 这是一个错误的名称:gdb_objc_realized_classes实际上是 命名类不在dyld共享缓存中,无论是否实现。 此列表排除了必须查找的惰性命名类 使用getClass钩子
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
for (EACH_HEADER) { ... } 发现类,修复未解决的未来类
当前在 read_images
环节,从镜像文件中读取machO的格式到表里边,然后通过地址还原到类里面,所以如何还原到类里面是该流程的重点,也就是 readClass
部分:
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// 图像已经充分优化,我们不需要调用readClass()
continue
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle()
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
// 从machO的数据表中读取到符号地址
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
/***********************************************************************
* readClass
* 读取编译器编写的类和元类
* 返回新的类指针。这可能是:
* - cls
* - nil ( cls 缺少一个弱链接超类 )
* - something else ( 这个类的空间被未来的类保留 )
*
* 注意,该函数执行的所有工作都是由preflightby执行的
* mustReadClasses(). 不要更改这个函数而不更新那个函数
*
* 锁定:由map_images或objc_readClassPair获取的runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
// 此处我们只匹配到自己添加的类来断点研究其 readClass 流程
if (0 == strcmp(mangledName, "SMPerson")) {
printf("\nSM 我们自己添加的类 需要重点关注的 -- %s - %s", __func__ , mangledName);
}
// nil ( cls 缺少一个弱链接超类 )
if (missingWeakSuperclass(cls)) { ... }
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) {
if (Class newCls = popFutureNamedClass(mangledName)) {
// 这个名称之前被分配为一个未来的类
// 复制objc_class到future class的结构
// 保留未来的rw数据块
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// 手动设置地址区分的ptrauthed字段
// 以便newCls获得正确的签名
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) {
// 共享缓存中内置的类列表
// Fixme严格断言因为重复而不能工作
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} 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();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
}
// 供以后参考:共享缓存从不包含MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
readClass
方法中在 if (mangledName != nullptr) { if (Class newCls = popFutureNamedClass(mangledName)) {
分支中,我们看到了有对类的 ro
、rw
的相关赋值,但是断点调试下来, 并不会执行到此分支。而是来到了之后到 if (headerIsPreoptimized && !replacing) { } else
的流程里面来:
// 添加name => cls到命名的非元类映射;警告重复的类名并保留旧的映射
// 将 类名 和 类地址 加到 gdb_objc_realized_classes 表里面做关联
addNamedClass(cls, mangledName, replacing);
// 向所有类的表中添加一个类。如果addMeta为true;自动添加类的元类
addClassTableEntry(cls);
但是,此时并没有将我们关注的类中的ro
、rw
数据,所以其赋值操作并不在此方法内部实现。但是有关联类名和类地址。
接下来,我们在 _read_images
流程中继续向下,发现关于类的相关操作的还有几处,跟着断点走下来,来到了 realizeClassWithoutSwift
内:
realizeClassWithoutSwift - { ro rw superClass isa }
/***********************************************************************
* realizeClassWithoutSwift
* 对类cls执行首次初始化
* 包括分配它的读写数据
* 没有执行任何swift端初始化
* 返回类的实际类结构
* 锁定:runtimeLock必须被调用者写锁定
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// 修复验证类不在共享缓存的未打开部分?
// 此处开辟了ro的内存空间地址,其内部并没有加载内容数据
// 举个例子:你买了个房子,现在只是写上了你的名字,但是房子里还是空着的,可能你去家具城定好了家具,但,此刻还没有布置到房子里去
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// 这是未来的课程。已经分配了Rw数据
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 正常的类。分配可写的类数据
// 将干净的内存 ro 复制一份到 rw
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// 为这个类选择一个索引
// 如果索引没有更多的索引可用,设置cls->instancesRequireRawIsa
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// 实现超类和元类,如果它们还没有实现的话
// 对于根类,需要在上面设置rw_realize之后完成
// 这需要在为根元类选择类索引之后进行
// 这假设这些类都没有Swift内容
// 或者Swift的初始化器已经被调用
// 修正一下,如果我们添加支持,这个假设将是错误的
// 对于Swift类的ObjC子类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// 元类不需要来自非指针ISA的任何特性
// 这允许为objc_retain/objc_release中的类提供一个路径
cls->setInstancesRequireRawIsa();
} else {
// 对于某些类和/或平台禁用非指针
// 设置 instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// 非指针是禁用的环境或应用程序SDK版本
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// libdispatch等人还充当虚函数表指针
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// 这也是由addSubclass()传播的
// 但是非指针需要更早的设置
// 特殊情况:instancesRequireRawIsa不传播
// 从根类到根元类
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// 更新超类和元类,以防重新映射
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// 协调实例变量偏移量/布局
// 这可能会重新分配class_ro_t,更新ro变量
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// 如果还没有设置,请设置fastInstanceSize
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// 从ro或from传播关联对象禁止标志
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// 将这个类连接到它的父类的子类列表
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// 附加类别
methodizeClass(cls, previously);
return cls;
}
methodizeClass(cls, previously)
/***********************************************************************
* methodizeClass
* 修复cls的方法列表、协议列表和属性列表
* 附加任何突出类别
* 锁定:runtimeLock必须由调用者持有
**********************************************************************/
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();
const char *mangledName = cls->nonlazyMangledName();
// 第一次系统化
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 安装类自身实现的方法和属性
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
// 并没有对 rwe 赋值, 什么时候赋值的呢?
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// 如果根类没有额外的方法实现,它们会得到额外的方法实现
// 他们已经,这些适用于类别替换之前
if (cls->isRootMetaclass()) {
// 根元类
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// 附加类别
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// 当一个类重新定位时,用类方法分类
// 可以注册在课程本身而不是
// 元类。告诉attachToClass寻找这些
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: 检查所有SELs;日志方法列表内容
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name()));
}
ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
}
#endif
}
prepareMethodLists
...
// 向数组中添加方法列表
// 重新分配未固定的方法列表
// 新方法被前置到方法列表数组中
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
// 如有必要,修正选择器
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锁定更少的attachMethodLists ?
// Dyld3可能已经对列表进行了惟一化,但没有排序
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// 列表中唯一的选择器
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
// 拿到SEL 将名字 和 地址 设置 到 meth 中
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
// 按选择器地址排序
// 不要试图对小列表进行排序,因为它们是不可变的
// 不要试图对非标准大小的大列表进行排序,如stable_sort
// 不能正确复制条目
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// 将方法列表标记为惟一的和已排序的
// 不能标记小列表,因为它们是不可变的
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
按照地址排序这一部分我们可以通过加代码答应来验证对于我们的SMPerson
类中方法的排序前后对比
懒加载
实现了 +load 方法的类就是非懒加载类。
我们将SMPerson
类中的+load方法注释掉, 然后在 realizeClassWithoutSwift
方法中 添加 上之前的 测试代码
const char *mangledName = cls->nonlazyMangledName();
if (0 == strcmp(mangledName, "SMPerson")) {
printf("\nSM realizeClassWithoutSwift -- %s - %s", __func__ , mangledName);
}
断点打好,接着运行项目后,常规命令 bt
查看下堆栈信息 :
我们发现此时的流程已经走到了 main()
函数之后, 此刻才是 SMPerson
类的数据处理。也就是说,懒加载类和非懒加载类的加载顺序是不同的,懒加载类将类信息的加载推迟到了第一次发消息的时候,也就是:
类的加载流程图
所以,类的加载流程是这样的:
关于分类
我们有一个SMPerson
名为 SM
的分类:
clang -rewrite-objc main.m -o main-cate.cpp
之后可以找到我们的分类,是一个 _category_t
类型的结构体:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_SMPerson_$_SM,
};
_category_t
// 分类没有元类
struct _category_t {
//扩展名字
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
SMPerson (SM)
static struct _category_t _OBJC_$_CATEGORY_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) =
{ // 静态编译时,并没有跑起runtime,故,先贴过来
"SMPerson",
0, // &OBJC_CLASS_$_SMPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_SMPerson_$_SM,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_SMPerson_$_SM,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_SMPerson_$_SM,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_SMPerson_$_SM,
};
static void OBJC_CATEGORY_SETUP_$_SMPerson_$_SM(void ) {
_OBJC_$_CATEGORY_SMPerson_$_SM.cls = &OBJC_CLASS_$_SMPerson;
}
_method_list_t
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"cate_instanceMethod2342134", "v16@0:8", (void *)_I_SMPerson_SM_cate_instanceMethod2342134},
{(struct objc_selector *)"cate_instanceMethod6758675", "v16@0:8", (void *)_I_SMPerson_SM_cate_instanceMethod6758675}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"cate_classMethod2462457821", "v16@0:8", (void *)_C_SMPerson_SM_cate_classMethod2462457821}}
};
那么,分类的数据是如何加载处理的呢?cpp
文件我们看到分类的信息是存储在_category_t
结构体中的,为什么我们可以直接通过SMPerson
直接调用的呢? 这是我们接下来探索的重点。
在 上面 methodizeClass
方法中,有对于 rwe
的赋值操作
auto rwe = rw->ext();
那么,就来到了 ext():
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
// 有 rwe 的时候 是可以获取得到的 否则 就 extAlloc 开辟
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
extAllocIfNeeded
的调取通过阅读源码发现是在一下情况下有调用:
attachCategories
、 objc_class::demangledName
、class_setVersion
、 addMethods_finish
、 class_addProtocol
、 _class_addProperty
、objc_duplicateClass
,那么,写一篇我们一起探索下关于类的加载部分关于分类加载
的内容。大家,加油!!
补充
运行时环境变量
添加方法: Project -> Scheme -> Edit Scheme ,在 Arguments 下可以添加运行时的环境变量( Environment Variables )
查看列表:打开终端执行以下命令可查看完整列表
$ export OBJC_HELP=1