<简书 — 刘小壮> https://www.jianshu.com/p/4fb2d7014e9egit
在iOS程序中会用到不少系统的动态库,这些动态库都是动态加载的。全部iOS程序共用一套系统动态库,在程序开始运行时才会开始连接动态库。github
除了在项目设置里显式出现的动态库外,还会有一些隐式存在的动态库。例如objc
和Runtime
所属的libobjc.dyld
和libSystem.dyld
,在libSystem
中包含经常使用的libdispatch(GCD)
、libsystem_c
(C语言基础库)、libsystem_blocks(Block)
等。数组
使用动态库的优势:app
App
公用一套系统动态库,防止重复的内存占用。在应用程序启动后,由dyld(the dynamic link editor)
进行程序的初始化操做。大概流程就像下面列出的步骤,其中第三、四、5步会执行屡次,在ImageLoader
加载新的image
进内存后就会执行一次。函数
dyld
将应用程序加载到二进制中,并完成一些文件的初始化操做。Runtime
向dyld
中注册回调函数。ImageLoader
将全部image
加载到内存中。dyld
在image
发生改变时,主动调用回调函数。Runtime
接收到dyld
的函数回调,开始执行map_images
、load_images
等操做,并回调+load
方法。main()
函数,开始执行业务代码。ImageLoader
是image
的加载器,image
能够理解为编译后的二进制。oop
下面是在Runtime
的map_images
函数打断点,观察回调状况的汇编代码。能够看出,调用是由dyld
发起的,由ImageLoader
通知dyld
进行调用。源码分析
关于dyld
我并无深刻研究,有兴趣的同窗能够到Github上下载源码研究一下。布局
一个OC程序能够在运行过程当中动态加载和连接新类或Category
,新类或Category
会加载到程序中,其处理方式和其余类是相同的。动态加载还能够作许多不一样的事,动态加载容许应用程序进行自定义处理。优化
OC提供了objc_loadModules
运行时函数,执行Mach-O
中模块的动态加载,在上层NSBundle
对象提供了更简单的访问API
。ui
在Runtime
加载时,会调用_objc_init
函数,并在内部注册三个函数指针。其中map_images
函数是初始化的关键,内部完成了大量Runtime
环境的初始化操做。
在map_images
函数中,内部也是作了一个调用中转。而后调用到map_images_nolock
函数,内部核心就是_read_images
函数。
void _objc_init(void)
{
// .... 各类init
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
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);
}
}
复制代码
在_read_images
函数中完成了大量的初始化操做,函数内部代码量比较大,下面是精简版带注释的源代码。
先总体梳理一遍_read_images
函数内部的逻辑:
gdb_objc_realized_classes
表中。SEL
都注册到namedSelectors
表中。Protocol
都添加到protocol_map
表中。Protocol
作重映射。rw
、ro
等操做。Category
,包括Class
和Meta Class
。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;
TimeLogger ts(PrintImageTimes);
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
// 实例化存储类的哈希表,而且根据当前类数量作动态扩容
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
// 由编译器读取类列表,并将全部类添加到类的哈希表中,而且标记懒加载的类并初始化内存空间
for (EACH_HEADER) {
if (! mustReadClasses(hi)) {
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
/** 将新类添加到哈希表中 */
// 从编译后的类列表中取出全部类,获取到的是一个classref_t类型的指针
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
// 数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及本身建立的类
Class cls = (Class)classlist[i];
// 经过readClass函数获取处理后的新类,内部主要操做ro和rw结构体
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// 初始化全部懒加载的类须要的内存空间
if (newCls != cls && newCls) {
// 将懒加载的类添加到数组中
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
// 将未映射Class和Super Class重映射,被remap的类都是非懒加载的类
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 重映射Class,注意是从_getObjc2ClassRefs函数中取出类的引用
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// 重映射父类
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
// 将全部SEL都注册到哈希表中,是另一张哈希表
static size_t UnfixedSelectors;
sel_lock();
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);
}
}
// 修复旧的函数指针调用遗留
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
for (i = 0; i < count; i++) {
// 内部将经常使用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
fixupMessageRef(refs+i);
}
}
// 遍历全部协议列表,而且将协议列表加载到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);
}
}
// 修复协议列表引用,优化后的images多是正确的,可是并不肯定
for (EACH_HEADER) {
// 须要注意到是,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不同
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
// 实现非懒加载的类,对于load方法和静态实例变量
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// 实现全部非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClass(cls);
}
}
// 遍历resolvedFutureClasses数组,实现全部懒加载的类
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
// 实现懒加载的类
realizeClass(resolvedFutureClasses[i]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
// 发现和处理全部Category
for (EACH_HEADER) {
// 外部循环遍历找到当前类,查找类对应的Category数组
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 内部循环遍历当前类的全部Category
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// 首先,经过其所属的类注册Category。若是这个类已经被实现,则从新构造类的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 将Category添加到对应Class的value中,value是Class对应的全部category数组
addUnattachedCategoryForClass(cat, cls, hi);
// 将Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
// 这块和上面逻辑同样,区别在于这块是对Meta Class作操做,而上面则是对Class作操做
// 根据下面的逻辑,从代码的角度来讲,是能够对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
// 初始化从磁盘中加载的全部类,发现Category必须是最后执行的
// 从runtime objc4-532版本源码来看,DebugNonFragileIvars字段一直是-1,因此不会进入这个方法中
if (DebugNonFragileIvars) {
realizeAllClasses();
}
#undef EACH_HEADER
}
复制代码
其内部还调用了不少其余函数,后面会详细介绍函数内部实现。
在项目中常常用到load
类方法,load
类方法的调用时机比main
函数还要靠前。load
方法是由系统来调用的,而且在整个程序运行期间,只会调用一次,因此能够在load
方法中执行一些只执行一次的操做。
通常Method Swizzling
都会放在load
方法中执行,这样在执行main
函数前,就能够对类方法进行交换。能够确保正式执行代码时,方法确定是被交换过的。
若是对一个类添加Category后,而且重写其原有方法,这样会致使Category中的方法覆盖原类的方法。可是load方法倒是例外,全部Category和原类的load方法都会被执行。
load
方法由Runtime
进行调用,下面咱们分析一下load
方法的实现,load
的实现源码都在objc-loadmethod.mm
中。
在一个新的工程中,咱们建立一个TestObject
类,并在其load
方法中打一个断点,看一下系统的调用堆栈。
从调用栈能够看出,是经过系统的动态连接器dyld
开始的调用,而后调到Runtime
的load_images
函数中。load_images
函数是经过_dyld_objc_notify_register
函数,将本身的函数指针注册给dyld
的。
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);
}
复制代码
在load_images
函数中主要作了两件事,首先经过prepare_load_methods
函数准备Class load list
和Category load list
,而后经过call_load_methods
函数调用已经准备好的两个方法列表。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!hasLoadMethods((const headerType *)mh)) return;
prepare_load_methods((const headerType *)mh);
call_load_methods();
}
复制代码
首先咱们看一下prepare_load_methods
函数的实现,看一下其内部是怎么查找load
方法的。能够看到,其内部主要分为两部分,查找Class
的load
方法列表和查找Category
方法列表。
准备类的方法列表时,首先经过_getObjc2NonlazyClassList
函数获取全部非懒加载类的列表,这时候获取到的是一个classref_t
类型的数组,而后遍历数组添加load
方法列表。
Category
过程也是相似,经过_getObjc2NonlazyCategoryList
函数获取全部非懒加载Category
的列表,获得一个category_t
类型的数组,须要注意的是这是一个指向指针的指针。而后对其进行遍历并添加到load
方法列表,Class
和Category
的load
方法列表是两个列表。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
// 获取到非懒加载的类的列表
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 设置Class的调用列表
schedule_class_load(remapClass(classlist[i]));
}
// 获取到非懒加载的Category列表
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
// 忽略弱连接的类别
if (!cls) continue;
// 实例化所属的类
realizeClass(cls);
// 设置Category的调用列表
add_category_to_loadable_list(cat);
}
}
复制代码
在添加类的load
方法列表时,内部会递归遍历把全部父类的load
方法都添加进去,顺着继承者链的顺序添加,会先把父类添加在前面。而后会调用add_class_to_loadable_list
函数,将本身的load
方法添加到对应的数组中。
static void schedule_class_load(Class cls)
{
if (!cls) return;
// 已经添加Class的load方法到调用列表中
if (cls->data()->flags & RW_LOADED) return;
// 确保super已经被添加到load列表中,默认是整个继承者链的顺序
schedule_class_load(cls->superclass);
// 将IMP和Class添加到调用列表
add_class_to_loadable_list(cls);
// 设置Class的flags,表示已经添加Class到调用列表中
cls->setInfo(RW_LOADED);
}
复制代码
而Category
则不须要考虑父类的问题,因此直接在prepare_load_methods
函数中遍历Category
数组,而后调用add_category_to_loadable_list
函数便可。
在add_category_to_loadable_list
函数中,会判断当前Category
有没有实现load
方法,若是没有则直接return
,若是实现了则添加到loadable_categories
数组中。
类的add_class_to_loadable_list
函数内部实现也是相似,区别在于类的数组叫作loadable_classes
。
void add_category_to_loadable_list(Category cat)
{
IMP method;
// 获取Category的load方法的IMP
method = _category_getLoadMethod(cat);
// 若是Category没有load方法则return
if (!method) return;
// 若是已使用大小等于数组大小,对数组进行动态扩容
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
复制代码
到此为止,loadable_classes
和loadable_categories
两个数组已经准备好了,load_images
会调用call_load_methods
函数执行这些load
方法。在这个方法中,call_class_loads
函数是负责调用类方法列表的,call_category_loads
负责调用Category
的方法列表。
void call_load_methods(void)
{
bool more_categories;
void *pool = objc_autoreleasePoolPush();
do {
// 反复执行call_class_loads函数,直到数组中没有可执行的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
复制代码
下面是调用类方法列表的代码,内部主要是经过对loadable_classes
数组进行遍历,并获取到loadable_class
的结构体,结构体中存在Class
和IMP
,而后直接调用便可。
Category
的调用方式和类的同样,就不在下面贴代码了。须要注意的是,load
方法都是直接调用的,并无走运行时的objc_msgSend
函数。
static void call_class_loads(void)
{
int i;
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
(*load_method)(cls, SEL_load);
}
if (classes) free(classes);
}
struct loadable_class {
Class cls; // may be nil
IMP method;
};
复制代码
根据上面的源码分析,咱们能够看出load
方法的调用顺序应该是 “父类 -> 子类 -> 分类” 的顺序。由于执行加载Class
的时机是在Category
以前的,并且load
子类以前会先load
父类,因此是这种顺序。
和load
方法相似的也有initialize
方法,initialize
方法也是由Runtime
进行调用的,本身不能够直接调用。与load
方法不一样的是,initialize
方法是在第一次调用类所属的方法时,才会调用initialize
方法,而load
方法是在main
函数以前就所有调用了。因此理论上来讲initialize
可能永远都不会执行,若是当前类的方法永远不被调用的话。
下面咱们研究一下
initialize
在Runtime
中的源码。
在向对象发送消息时,lookUpImpOrForward
函数中会判断当前类是否被初始化,若是没有被初始化,则先进行初始化再调用类的方法。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
// ....省略好多代码
// 第一次调用当前类的话,执行initialize的代码
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
// ....省略好多代码
复制代码
在进行初始化的时候,和load
方法的调用顺序同样,会按照继承者链先初始化父类。_class_initialize
函数中关键的两行代码是callInitialize
和lockAndFinishInitializing
的调用。
// 第一次调用类的方法,初始化类对象
void _class_initialize(Class cls)
{
Class supercls;
bool reallyInitialize = NO;
// 递归初始化父类。initizlize不用显式的调用super,由于runtime已经在内部调用了
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
performForkChildInitialize(cls, supercls);
return;
}
@try {
// 经过objc_msgSend()函数调用initialize方法
callInitialize(cls);
}
@catch (...) {
@throw;
}
@finally {
// 执行initialize方法后,进行系统的initialize过程
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
}
复制代码
经过objc_msgSend
函数调用initialize
方法。
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
复制代码
lockAndFinishInitializing
函数中会完成一些初始化操做,其内部会调用_finishInitializing
函数,在函数内部会调用class
的setInitialized
函数,核心工做都由setInitialized
函数完成。
static void lockAndFinishInitializing(Class cls, Class supercls)
{
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
复制代码
负责初始化类和元类,函数内部主要是查找当前类和元类中是否认义了某些方法,而后根据查找结果设置类和元类的一些标志位。
void
objc_class::setInitialized()
{
Class metacls;
Class cls;
// 获取类和元类对象
cls = (Class)this;
metacls = cls->ISA();
bool inherited;
bool metaCustomAWZ = NO;
if (MetaclassNSObjectAWZSwizzled) {
metaCustomAWZ = YES;
inherited = NO;
}
else if (metacls == classNSObject()->ISA()) {
// 查找是否实现了alloc和allocWithZone方法
auto& methods = metacls->data()->methods;
for (auto mlists = methods.beginCategoryMethodLists(),
end = methods.endCategoryMethodLists(metacls);
mlists != end;
++mlists)
{
if (methodListImplementsAWZ(*mlists)) {
metaCustomAWZ = YES;
inherited = NO;
break;
}
}
}
else if (metacls->superclass->hasCustomAWZ()) {
metaCustomAWZ = YES;
inherited = YES;
}
else {
auto& methods = metacls->data()->methods;
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
if (methodListImplementsAWZ(*mlists)) {
metaCustomAWZ = YES;
inherited = NO;
break;
}
}
}
if (!metaCustomAWZ) metacls->setHasDefaultAWZ();
if (PrintCustomAWZ && metaCustomAWZ) metacls->printCustomAWZ(inherited);
bool clsCustomRR = NO;
if (ClassNSObjectRRSwizzled) {
clsCustomRR = YES;
inherited = NO;
}
// 查找元类是否实现MRC方法,若是是则进入if语句中
if (cls == classNSObject()) {
auto& methods = cls->data()->methods;
for (auto mlists = methods.beginCategoryMethodLists(),
end = methods.endCategoryMethodLists(cls);
mlists != end;
++mlists)
{
if (methodListImplementsRR(*mlists)) {
clsCustomRR = YES;
inherited = NO;
break;
}
}
}
else if (!cls->superclass) {
clsCustomRR = YES;
inherited = NO;
}
else if (cls->superclass->hasCustomRR()) {
clsCustomRR = YES;
inherited = YES;
}
else {
// 查找类是否实现MRC方法,若是是则进入if语句中
auto& methods = cls->data()->methods;
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
if (methodListImplementsRR(*mlists)) {
clsCustomRR = YES;
inherited = NO;
break;
}
}
}
if (!clsCustomRR) cls->setHasDefaultRR();
if (PrintCustomRR && clsCustomRR) cls->printCustomRR(inherited);
metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
}
复制代码
须要注意的是,initialize
方法和load
方法不太同样,Category
中定义的initialize
方法会覆盖原方法而不是像load
方法同样均可以调用。
简书因为排版的问题,阅读体验并很差,布局、图片显示、代码等不少问题。因此建议到我Github
上,下载Runtime PDF
合集。把全部Runtime
文章总计九篇,都写在这个PDF
中,并且左侧有目录,方便阅读。
下载地址:Runtime PDF 麻烦各位大佬点个赞,谢谢!😁