iOS底层原理之类的加载处理
2020-01-10 本文已影响0人
尘舒
1. _objc_init
- 程序在启动时,先用dyld进行动态库的链接,做完一系列准备操作之后,会进入到_objc_init方法
- 下面简单了解一下主角_dyld_objc_notify_register开始之前的各个初始化的目的,在此不做详细介绍了,因为我也介绍不清楚~
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);
}
1.1 environ_init
- 读取影响运行时的环境变量,可以使用 export OBJC_HELP=1 来打印环境变量帮助,下面展示一下打印的示例
objc[3206]: Objective-C runtime debugging. Set variable=YES to enable.
objc[3206]: OBJC_HELP: describe available environment variables
objc[3206]: OBJC_PRINT_OPTIONS: list which options are set
objc[3206]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[3206]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[3206]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[3206]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[3206]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[3206]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[3206]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[3206]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[3206]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[3206]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[3206]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
1.2 tls_init
- 关于线程key的绑定
1.3 static_init
- 运行C++静态构造函数,在dyld调用我们的静态构造函数之前,libc会调用_objc_init,所以需要我们自己做
1.4 lock_init
- 空实现
1.5 exception_init
- 初始化 libobjc的异常处理系统
1.6 小结
- 做完上述准备条件之后,开始进入主角_dyld_objc_notify_register,通过注释可以知道该方法仅仅只用来在objc的运行时使用 ,进行map_images和load_images
//
// 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);
2. 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读取镜像文件到内存,把类、分类、属性、方法、协议等全部读取到内存中去
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
2.1 read_images
- read_images的内部,通过定义个静态变量doneOnce,保证操作只做一次
// 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);
- 当第一次执行时,会创建一张包含所有的类和元类的表gdb_objc_realized_classes,外加一张只包含已经初始化的类的表allocatedClasses
- 具体流程如下
// 1. 初次进入,创建两张表
if (!doneOnce) {
doneOnce = YES;
/** ... */
}
// 2. 类处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: discover classes");
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) {
for (EACH_HEADER) {...}
}
ts.log("IMAGE TIMES: remap classes");
// 3. 方法编号处理
// Fix up @selector references
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: fix up selector references");
// 修复处理,一般不会走
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
// 4. 协议处理
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: discover protocols");
// 修复处理,忽略
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: fix up @protocol references");
// 5. 非懒加载类的处理
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: realize non-lazy classes");
// 6. 将来需要处理的类
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {...}
ts.log("IMAGE TIMES: realize future classes");
// 7. 分类处理
// Discover categories.
for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: discover categories");
2.2 类的处理
2.2.1 分析
- 那么再回到 read_images中,当创建完两张表之后,会进行类的处理,那么现在来分析一下,具体的处理过程
- 1.首先遍历当前的classlist,获取一下当前的类cls,此时只能获取到一个占位空间,没有具体信息
- 2.通过readClass读取cls的信息,读取完之后赋值给newCls,此时拿到了类的相关信息
for (i = 0; i < count; i++) {
//数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。 以及自己创建的类,
//此时只有地址,还没有获取到具体的类名,类的信息在下一步获取
Class cls = (Class)classlist[i];
// 通过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;
}
}
2.2.2 readClass初步分析
- 进入到readClass内部,我们可以看到如下代码,包含了class的rw以及ro等等赋值,我们的第一反应就是在readClass的时候进行rw和ro的读取
Class replacing = nil;
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
printf("******** popFutureNamedClass \n");
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));
rw->ro = (class_ro_t *)newCls->data();
newCls->setData(rw);
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
- 那么我们手动试验一下,先在if内部增加一个打印方法
printf("******** popFutureNamedClass \n");
- 为了更准确知道到底有没有在这里进行赋值,我们在判断之前,先用一段代码打印一下加载的类的信息
// 验证方式, 这段代码放在上面那段 上面执行
const class_ro_t *lgro = (const class_ro_t *)cls->data();
const char *lgname = lgro->name;
const char *lgnameTest = "LGPerson";
if (strcmp(lgname, lgnameTest) == 0) {
printf("******** LGPerson\n");
}
- 看一下输出结果,popFutureNamedClass没有被打印,LGPerson被打印出来,可以发现当我们自定义的类LGPerson被加载的时候,并没有走到方法内部,而系统类也没有走到该方法内部,也就是说不满足popFutureNamedClass的条件,所以基本可以确定,一般的类不会在这里进行操作,这里是专门针对未来的待处理的类的特殊操作
******** LGPerson
2020-01-09 23:16:22.008399+0800 objc-debug[4032:140200] +[LGTeacher load]
2020-01-09 23:16:22.008904+0800 objc-debug[4032:140200] +[LGStudent load]
2.2.3 readClass进一步探索
- 我们知道普通的类并不会走到上面的if条件内部,那么接着看下面,其中在else中出现了两个方法addNamedClass和addClassTableEntry,分别追踪其方法内部,
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// assert(cls == getClass(name));
assert(getClassExceptSomeSwift(mangledName));
} else {
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
}
- addNamedClass是把当前类cls添加到总表gdb_objc_realized_classes中去
/***********************************************************************
* 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是把当前类cls添加到,已经分配好的类的表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);
}
- 到此,当前类cls已经通过readClass被成功插入到两张表中
2.2.4 readClass延伸
- readClass之后,我们拿到了一个被处理后的新的类newCls,当newCls和cls不相同之后就会走特殊处理
- 但是通过刚才readClass我们可以知道,一般的系统类和自定义类走完方法之后并没有改变,只有当有符合popFutureNamedClass条件的类出现的时候,我们才会走特殊的if处理方法,并且在最后给cls赋值,cls = newCls(代码可以看2.2.2第一段代码最后),所以外面这个if判断,对于一般类来说是肯定不会走的
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;
}
2.3 方法编号和协议处理
- 通过_getObjc2SelectorRefs读取所有的方法,并且把名字注册到当前的内存中去
- 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
// 将所有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);
}
}
}
// 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);
}
}
2.4 非懒加载类的处理
- 针对非懒加载的类的处理,还是先从镜像表中映射出来cls后,然后进行一些isa和cache的初始化赋值
- 再次插入到表allocatedClasses中
- realizeClassWithoutSwift实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
// 实现非懒加载的类,对于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 didn't 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't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
// 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClassWithoutSwift(cls);
}
}
2.4.1 realizeClassWithoutSwift
- 进入到方法内部,可以看到ro的赋值,此时ro已经有了值,但是rw只是创建,并没有值
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);
}
- 继续往下,通过类的继承链,不断往上级父类递归处理,直到cls为nil的时候,则递归中断退出,从之前的isa和superClass继承图上,我们可以了解到,NSObject的父类为nil,那么刚好,这个递归,一直找到继承链的顶端也就是NSObject为止
if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
cls->superclass = supercls;
cls->initClassIsa(metacls);
2.4.2 methodizeClass
- 在realizeClassWithoutSwift方法即将返回之前,我们可以看到,系统还调用了methodizeClass方法
static Class realizeClassWithoutSwift(Class cls)
{
// 都是代码
// Attach categories
methodizeClass(cls);
return cls;
}
- methodizeClass通过attachLists将methods、properties以及protocols写入到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]));
}
}
- 到此,非懒加载类的rw也已经赋值完成
2.5 懒加载类的处理
- 没有实现load方法的类,叫做懒加载类
- 当懒加载类第一次调用消息发送的时候,会去实现该类
- 在lookUpImpOrForward方法中,当isRealized条件满足时,会去调用方法realizeClassMaybeSwiftAndLeaveLocked
- 最后调用realizeClassWithoutSwift(cls)和之前类似处理
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
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;
}
2.6 分类的处理
2.6.1 分类的定义
- 自定义分类LGTeacher+test,反编译成C++文件以后,发现分类结构体
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;
};
- 在objc源码中objc-runtime-new.h里面定义到分类结构体
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
2.6.2 分类的加载
2.6.2.1 非懒加载主类+非懒加载分类
- 1.read_images
- 2.realizeClassWithoutSwift
- 3.methodizeClass(cls)
- 4.unattachedCategoriesForClass
- 5.attachCategories
2.6.2.2 非懒加载主类+懒加载分类
- 1.read_images
- 2.realizeClassWithoutSwift
- 3.methodizeClass(cls)
- 4.直接添加到data->ro
2.6.2.3 懒加载主类+懒加载分类
- 1.ObjcMsgSend
- 2.lookuporforward
- 3.realizeClassWithoutSwift
- 4.methodlizeClass
2.6.2.4 懒加载主类+非懒加载分类
- 这个时候,主类需要提前实现
- 提前到prepare_load_methods实现
3. 总结
- 1._objc_init先进行初始化设置
- 2.通知进行map_images,其中关键操作就是read_images
- 3.read_images内部先把类添加到表gdb_objc_realized_classes和allocatedClasses中去,之后把方法编号和协议等也映射到内存中去,并在对非懒加载类进行处理的时候,通过realizeClassWithoutSwift对ro进行赋值,并且创建rw,之后通过methodizeClass对rw赋值
- 4.对于懒加载类处理的时候,则是在第一次消息发送的时候进行处理
- 5.对于分类的处理的时候,需要考虑到主类和分类是否分别为懒加载类,需要分开处理,当分类为懒加载类时,编译时就确定,如果为非懒加载分类,那么就是运行时确定,
- 这一块理解的有点迷~~~,有错误的地方希望大家补充指正