前篇内容回顾应用程序的加载(二)
-
dyld
在初始化主程序initializeMainExecutable()
的时候会初始化所有的镜像文件doInitialization()
- 在初始化动态库的时候会优先保证
libSystem
库的初始化。 - 而
libSystem
库初始化的时候又会来到libobjc.A.dylib
中的_objc_init()
中。 - 这样就从
dyld
中来到了runtime
的重要程序_objc_init()
。 -
_objc_init()
中完成了向dyld
的几个重要函数的注册:_dyld_objc_notify_register(&map_images, load_images, unmap_image);
- 在
dyld
完成初始化doInitialization()
之后就会发送通知notifySingle()
,进行下一步处理。
类的加载过程
现在看看objc
向dyld
注册的几个函数的作用
-
map_images
:主要是读取镜像文件,把类的相关信息加载到内存中。 -
load_images
:load
方法的初始化调用。
map_images
map_images
的调用时机就是在_dyld_objc_notify_register
内部完成函数注册后。
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
}
map_images
函数内部最重要的一步就是读取镜像文件:
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
_read_images
这一步,主要是读取加载类的信息,按照官方文档注释主要有一下几个步骤:
1、初始化参数配置。
2、@selector方法读取
3、类的读取
4、修复重映射一些没有被镜像文件加载进来的类
5、一些特定消息函数指针修改
6、类协议读取
7、修复没有被加载的协议
8、分类处理
9、类的加载处理
10、没有被处理的类,优化那些被破坏的类
1.初始化参数配置
这里有targetpointer
对象的初始化。关于targetpointer
参考WWDC2020 Class数据结构变化
然后就是创建NXMapTable
,这个表是用来存放类的。方便将来的快速查找用。
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
initializeTaggedPointerObfuscator();
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
2.@selector方法读取
实际上是把这些方法用namedSelectors
这个表存放起来。
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) selLock.assertUnlocked();
else selLock.assertLocked();
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
auto it = namedSelectors.get().insert(name);
if (it.second) {
// No match. Insert.
*it.first = (const char *)sel_alloc(name, copy);
}
return (SEL)*it.first;
}
3.类的读取
这里主要的一点就是readClass()
,在这之前 cls只是一个指针地址,还没有和类进行关联。经过了readClass()
之后,才能正式关联到类。
// 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;
}
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;
}
}
}
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized){
if (missingWeakSuperclass(cls)) {...}
if (Class newCls = popFutureNamedClass(mangledName)) {...}
if (headerIsPreoptimized && !replacing) {…}
else {
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
}
}
readclass
基本思路就是:
- 找不到该类的父类,可能
weak-linked
,直接返回nil; - 找到类了,类是否是一个
future class
(简单理解为将来可能改变的类),如果有变化则创建新类,并把旧类的数据拷贝一份然后赋值给新类newCls,然后调用addRemappedClass进行重映射,用新的类替换掉旧的类,并返回新类newCls的地址 - 找到类了,如果类没有任何变化,则不进行任何操作,直接返回class
-
addNamedClass()
已经读取完成的类,会被存放到了这个表gdb_objc_realized_classes
里面 -
addClassTableEntry ()
里面会把这个读取完成的类添加到allocatedClasses
表里面,然后再判断addMeta
是否为YES,然后会把这个类的元类也添加到allocatedClasses这个表里面。这是一个递归调用过程,只有这个类第一次进来的时候addMeta
为真,之后的递归调用addMeta
为假,保证了元类只被添加一次。
4.修复重映射一些没有被镜像文件加载进来的类
- fix up重映射的类
- 类列表和nolazy类列表保持未映射
- 类引用和super引用将重新映射用于进行消息分发
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
5.一些特定消息函数指针修改
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
这里对一些特殊的消息的函数地址进行了修改,比如调用alloc
方法的时候,虽然源码流程没有objc_alloc
但是实际上alloc
的实现地址被指向了objc_alloc
。
这里需要修改的方法多是系统自己的一些方法,从mac-o的静态段__objc_msgrefs
中获取的。
6.类协议读取
主要还是看readProtocol()
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
读取类的协议,基本流程就是把读取的协议加入到protocol_map
表中。
7. remapProtocolRef
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
runtimeLock.assertLocked();
protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
if (*protoref != newproto) {
*protoref = newproto;
UnfixedProtocolReferences++;
}
}
8.读取分类
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
9.nolayz类的加载
虽然前面已经读取过类,但是正常情况下并没完全加载类的信息,这里才是正常加载类的信息。
for (EACH_HEADER) {
…
addClassTableEntry(cls);
realizeClassWithoutSwift(cls, nil);
…
}
10.newly-resolved future classes处理
如果是一些被改动过的类,会被重新创建并加载到内存中并实现。
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
realizeClassWithoutSwift 类信息的加载
类能够使用的前提是类的信息已经正确加载到内存中,在发送消息的时候都会判断这个类是否已经实现了。
realizeClassWithoutSwift()
的作用就是把类的信息加载到内存中。
- 读取类的data
编译时类在内存中的位置就确定了,ro
存放的类名称、方法、协议和实例变量的信息,是只读的。rw
则是由于类的信息可能发生改变,比如添加属性、方法、添加协议,实际上绝大多数的类都不会改变,因此为了优化,出现了ro_or_rw_ext
用于存放改变的信息。
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 = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
…
methodizeClass(cls, previously);
- 完成继承链的实现
依照isa
的继承关系递归调用realizeClassWithoutSwift()
完成父类、元类的实现。 - 加载分类
methodizeClass()
-
methodizeClass()
从ro
中读取方法列表、属性列表、协议列表并添加到rwe
中。 - 添加分类的方法。
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();
...
method_list_t *list = ro->baseMethods();//获取ro的baseMethods
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
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()) {
// 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 {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
....
}
attachToClass 添加分类方法
methodlist
内部添加分类的方法单独列出来。
分类的添加相对比较麻烦,因为分类中包含了属性、方法、协议,但没有成员变量。
同时也会初始化rwe
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
关于attachLists()
以添加分类的方法为例,方法在类的存储结构中是以二维数组的形式存在,本类的方法列表和分类的方法列表都是作为二维数组中的一维数组元素。
每当有新的分类的list要添加到类的信息中时,会直接先重新开辟新的二维数组,大小为之前二维数组的容量加新分类list,然后把新的list放在扩容后的二维数组前面,旧的list顺位向后内存平移。
这样一来,后添加的分类的方法总是排在类的方法列表的前面,那么类在发送消息的时候如果出现分类方法和本类方法重名的情况,总会调用位置靠前的分类方法,造成了一种分类方法覆盖了本类方法的错觉,实际上本类方法依然还在,但是没机会调用而已。
网友评论