在上一篇我们分析了_objc_init
方法,程序运行时,dyld将使用包含objc_image_info
的镜像文件数组,回调 mapped
函数,最后会执行libObjc
的map_images
方法
map_images的主要流程
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_nolock方法
- 查看
map_images_nolock
方法,里面代码比较多,直接看重点
_read_images方法
- 查看
_read_images
方法
_read_images
内容比较多,根据苹果注释信息,主要有以下几步:
- 条件控制进行一次加载
- 修复预编译阶段的
@selector
的混乱的问题 - 错误混乱的类处理
- 修复重映射一些没有被镜像文件加载进来的类
- 修复一些消息
- 当类中有协议时:
readProtocol
- 修复没有被加载的协议
- 分类的处理
- 类的加载处理
- 没有被处理的类,优化那些被侵犯的类
条件控制进行一次加载
doneOnce
是全局静态变量,加载一次后doneOnce=YES
,下次就不会在进入判断。第一次进来主要创建表gdb_objc_realized_classes
,表里存放的是不在dyld共享缓存中的命名类,无论是否实现
static bool doneOnce;
if (!doneOnce) {
doneOnce = YES; // doneOnce:全局静态变量,只加载一次
launchTime = YES;
// ...此处省略代码
// namedClasses 是不在dyld共享缓存中的命名类,无论是否实现。
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor 4/3是NXMapTable的负载系数
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// 创建哈希表 存放所有的类
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
修复@selector的混乱
- 修复@selector的混乱,从macho文件中获取对象方法列表,方法列表存放在
DATA段
的__objc_selrefs
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
// 从macho文件中获取方法名列表,方法列表存放在 DATA段 的 __objc_selrefs
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) {
// TODO:自定义打印
_objc_inform("=======修复@selector的混乱:%p = %p", (SEL)sels[i], sel);
sels[i] = sel;
}
}
}
}
错误混乱的类处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
// 发现类。修复未解析的未来类。标记捆绑类
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
// 从Mach-O中读取类列表信息,类总数count
classref_t const *classlist = _getObjc2ClassList(hi, &count);
if (hIndex == hCount - 1) {
_objc_inform("=======发现类。修复未解析的未来类。标记捆绑类。:HTTest = %zu", 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;
}
}
}
- 自定义一个类
HTPerson
,设置相应的断点
- 执行
readClass
方法后,通过lldb查看cls的值,发现已经跟类名关联上了
👇我们来探究readClass
方法,看看他做了什么操作
readClass方法
这个方法主要是用来更新两张表:类名称表gdb_objc_realized_classes
, 所有类的表allocatedClasses
通过断点调试,我们发现readClass
主要有三步操作:
- 获取类名
mangledName
- 将类名和地址关联起来
- 添加【类和元类】到
所有类的表
(allocatedClasses
表)中,就是runtime_init
中开辟的那个表
获取类名
- 查看
nonlazyMangledName
方法,内部是通过bites属性
找到ro数据
,类名存放在class_ro_t
结构中
// Get the class's mangled name, or NULL if the class has a lazy
// name that hasn't been created yet.
const char *nonlazyMangledName() const {
return bits.safe_ro()->getName();
}
- 查看
safe_ro
方法,内部就是获取ro
结构体数据,因为class_ro_t
结构体中存储了类名
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
// 已实现的类通过 bits -> rw -> ro来获取
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
// 未加载过的类bits属性 存储的就是 ro结构体数据
return (class_ro_t *)maybe_rw;
}
}
addNamedClass将类名和地址关联绑定起来
- 查看
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 {
// 更新gdb_objc_realized_classes表,将key设置为 name value 设置为cls
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());
}
- 更新
gdb_objc_realized_classes
哈希表,key
是name
,value
是cls
添加【类和元类】到 所有类的表中
- 查看
addClassTableEntry
方法,源码如下,这一步就是将类和元类
加入到allocatedClasses
表中
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.
// _objc_init -> runtime_init 中初始化的表:所有类的表
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
// 将元类插入哈希表中
addClassTableEntry(cls->ISA(), false);
}
非懒加载类的加载
// +load方法的调用,是在 load_images方法中
// +load handled by prepare_load_methods()
// 加载非懒加载类
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
// 从Mach-O中读取非懒加载类列表信息,非懒加载类总数count
classref_t const *classlist = hi->nlclslist(&count);
if (hIndex == hCount - 1) {
_objc_inform("=======加载非懒加载类:HTTest = %zu", count);
}
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 添加【非懒加载类和他的元类】到 所有类的表中
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);
}
}
- 非懒加载类:实现了
+ (void)load
方法的类,程序启动时会加载 - 懒加载类:没有实现
+ (void)load
方法的类,在类首次使用时才会加载 - 可以查看
可执行文件
的Mach-O
,在DATA段
的__objc_nlclslist
中存放的是非懒加载类
现在有两个类:HTPerson
和HTTeacher
,其中HTPerson
类实现了+ (void)load
方法,运行程序,通过MachOView
查看可执行文件
- 设置相应的断点,通过
lldb
打印cls
,发现此时只有一个非懒加载类,即HTPerson
类,地址为0x00000001000082e8
,与Mach-O
的非懒加载类表相对应
- 接下来我们继续分析最重要的一个方法,
realizeClassWithoutSwift
类的加载
realizeClassWithoutSwift方法分析
realizeClassWithoutSwift
对类cls执行首次初始化
,包括分配其读写数据
(即 rw
数据,用于运行时记录类的信息),返回类的实际类结构
。
-
非懒加载类
在程序启动时
,就会执行realizeClassWithoutSwift
方法 -
懒加载类
在使用时才会去加载,我们在方法慢速查找时
有看到过,执行流程:lookUpImpOrForward
-->realizeAndInitializeIfNeeded_locked
-->realizeClassMaybeSwiftAndLeaveLocked
-->realizeClassMaybeSwiftMaybeRelock
-->realizeClassWithoutSwift
-
realizeClassWithoutSwift
方法源码如下,代码比较多,源码如下
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));
// fixme verify class is not in an un-dlopened part of the shared cache?
// 获取ro数据,类未初始化时,类结构objc_class的 bits属性存储的其实是 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 {
// Normal class. Allocate writeable class data.
// 开辟可读写数据,即rw,bits属性此时存储的是 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
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
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)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
// 递归实现 父类和元类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
// 设置superclass 和 isa
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
// 设置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();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
// 建立类 子类的双向链表关系
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
// 附加类别 - 方法化当前的类,方法排序,对类进行扩展
methodizeClass(cls, previously);
return cls;
}
realizeClassWithoutSwift
主要进行了下列几步操作:
1、rw初始化
,这里涉及到干净内存clean memory
和脏内存dirty memory
的概念。
-
ro属于clean memory,在编译时即确定的内存空间
,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息; -
rw
的数据空间属于dirty memory
,rw
是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存
。 -
rwe
类的额外信息。在WWDC2020
中也提到,只有不到10%
的类真正的更改了他们的方法,并不是每一个类都需要插入数据,进行修改的类很少,避免资源的消耗,所以就有了rwe
。
2、递归处理,进行父类和元类的实现。
3、isa处理
,在前面学习isa
的时候,对于NONPOINTER_ISA
进行了位域处理,指针优化,isa
的末尾位是1
,isa
不单单代表一个指针
。而对于元类以及特殊情况下的场景的一些类,无需开启指针优化的类,使用Raw Isa
,isa
的末尾位是0。
4、设置superclass
和 isa
属性,用来获取父类和元类
5、设置fastInstanceSize
,编译器快速计算对象内存大小
6、c++析构函数
的相关设置,以及关联对象
的相关设置。
7、建立子类与父类的双定链表关系
,保证子类能找到父类,父类也可以找到子类。
8、方法化当前的类,向类中添加方法,协议、属性,同时对方法列表进行排序等操作。
methodizeClass方法分析
methodizeClass
方法源码如下:
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();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
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);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
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
}
-
1、对方法、属性、协议进行处理。见下图:
从上图可以发现:rwe为空
,很显然,此时还没有对类进行相关的扩展操作,所以rwe
还没有被创建初始化。此时针对方法、属性、协议的添加操作时无效的
! 2、方法列表的处理中有些不同,调用了
prepareMethodLists
方法。那么该方法做了哪些操作呢?见下图:
核心流程,fixupMethodList
,根据注释:根据需要对selector
进行修复。进入fixupMethodList方法
,查看实现流程。见下图:
- 继续
methodizeClass
源码的解读。找到了类初始化过程中非常关键的步骤,向类中添加分类方法、协议等,rwe
的初始化也在其中。