前言
书接上回dyld & objc的关联,我们知道了系统在objc库的_objc_init
函数中注册了关于镜像文件读取、加载和移除的回调函数,然后在dyld链接的过程去触发这些回调,告知objc库去加载类信息等一系列操作。今天我们大致分析下类的加载的具体流程。
1 _objc_init大致流程
先看源码:
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();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
一眼看去,首先进行了一系列的初始化流程,然后注册回调函数_dyld_objc_notify_register
,这就是之前提的关于镜像文件读取、加载和移除的三个回调函数。
先看看初始化流程,大致做了哪些工作:
1.1 environ_init - 环境变量的初始化
举例说明:
- 打开项目
Edit Scheme...
2.设置环境变量,例如OBJC_PRINT_LOAD_METHODS = YES
3.run
项目
一些环境变量设置:
环境变量名 | 说明 |
---|---|
OBJC_PRINT_OPTIONS | 输出OBJC已设置的选项 |
OBJC_PRINT_IMAGES | 输出已load的image信息 |
OBJC_PRINT_LOAD_METHODS | 打印 Class 及 Category 的 + (void)load 方法的调用信息 |
OBJC_PRINT_INITIALIZE_METHODS | 打印 Class 的 + (void)initialize 的调用信息 |
OBJC_PRINT_RESOLVED_METHODS | 打印通过 +resolveClassMethod: 或 +resolveInstanceMethod: 生成的类方法 |
OBJC_PRINT_CLASS_SETUP | 打印 Class 及 Category 的设置过程 |
OBJC_PRINT_PROTOCOL_SETUP | 打印 Protocol 的设置过程 |
OBJC_PRINT_IVAR_SETUP | 打印 Ivar 的设置过程 |
OBJC_PRINT_VTABLE_SETUP | 打印 vtable 的设置过程 |
OBJC_PRINT_VTABLE_IMAGES | 打印 vtable 被覆盖的方法 |
OBJC_PRINT_CACHE_SETUP | 打印方法缓存的设置过程 |
OBJC_PRINT_FUTURE_CLASSES | 打印从 CFType 无缝转换到 NSObject 将要使用的类(如 CFArrayRef 到 NSArray * ) |
OBJC_PRINT_GC | 打印一些垃圾回收操作 |
OBJC_PRINT_PREOPTIMIZATION | 打印 dyld 共享缓存优化前的问候语 |
OBJC_PRINT_CXX_CTORS | 打印类实例中的 C++ 对象的构造与析构调用 |
OBJC_PRINT_EXCEPTIONS | 打印异常处理 |
OBJC_PRINT_EXCEPTION_THROW | 打印所有异常抛出时的 Backtrace |
OBJC_PRINT_ALT_HANDLERS | 打印 alt 操作异常处理 |
OBJC_PRINT_REPLACED_METHODS | 打印被 Category 替换的方法 |
OBJC_PRINT_DEPRECATION_WARNINGS | 打印所有过时的方法调用 |
OBJC_PRINT_POOL_HIGHWATER | 打印 autoreleasepool 高水位警告 |
OBJC_PRINT_CUSTOM_RR | 打印含有未优化的自定义 retain/release 方法的类 |
OBJC_PRINT_CUSTOM_AWZ | 打印含有未优化的自定义 allocWithZone 方法的类 |
OBJC_PRINT_RAW_ISA | 打印需要访问原始 isa 指针的类 |
OBJC_DEBUG_UNLOAD | 卸载有不良行为的 Bundle 时打印警告 |
OBJC_DEBUG_FRAGILE_SUPERCLASSES | 当子类可能被对父类的修改破坏时打印警告 |
OBJC_DEBUG_FINALIZERS | 警告实现了 -dealloc 却没有实现 -finalize 的类 |
OBJC_DEBUG_NIL_SYNC | 警告 @synchronized(nil) 调用,这种情况不会加锁 |
OBJC_DEBUG_NONFRAGILE_IVARS | 打印突发地重新布置 non-fragile ivars 的行为 |
OBJC_DEBUG_ALT_HANDLERS | 记录更多的 alt 操作错误信息 |
OBJC_DEBUG_MISSING_POOLS | 警告没有 pool 的情况下使用 autorelease,可能内存泄漏 |
OBJC_DEBUG_DUPLICATE_CLASSES | 当出现类重名时停机 |
OBJC_USE_INTERNAL_ZONE | 在一个专用的 malloc 区分配运行时数据 |
OBJC_DISABLE_GC | 强行关闭自动垃圾回收,即使可执行文件需要垃圾回收 |
OBJC_DISABLE_VTABLES | 关闭 vtable 分发 |
OBJC_DISABLE_PREOPTIMIZATION | 关闭 dyld 共享缓存优化前的问候语 |
OBJC_DISABLE_TAGGED_POINTERS | 关闭 NSNumber 等的 tagged pointer 优化 |
OBJC_DISABLE_NONPOINTER_ISA | 关闭 non-pointer isa 字段的访问 |
1.2 tls_init - 线程相关初始化
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
pthread线程的key值处理。
1.3 static_init - 系统的C++ 静态构造函数初始化
1.4 runtime_init
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
我们发现
unattachedCategories
和allocatedClasses
都是继承ExplicitInit
,并且是一个集合类
,DenseMap
是在llvm中用的非常广泛的数据结构,它本身的实现是一个基于Quadratic probing
(二次探查)的散列表。
由此可见,
runtime_init
其实就是给objc
的类unattachedCategories
和allocatedClasses
进行初始化,各自生成一个散列表。
1.5 exception_init - libobjc的异常处理系统初始化
1.6 cache_init - cache缓存的初始化
这个task是mach_task_self()
由上图可见,mach_task_self()
实际上是mach上的一个端口__darwin_mach_port_t
,Mach端口是Mac OS X中进程通信的一种方式,那么缓存cache的初始化的实质就是在mach上的一个端口的指定过程。
1.7 _imp_implementationWithBlock_init - 启动回调机制
该方法主要是启动回调机制
,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载libobjc-trampolines.dylib。
1.8 _dyld_objc_notify_register
这个上回dyld & objc的关联已经分析了,它是dyld链接库时的所需回调函数的注册。dyld通过调用这些回调,通知objc去加载镜像文件,其中就包含了类的加载
,也是我们今天要重点分析的对象。
2. 类的加载流程
首先看看3个回调函数,哪个是关联到类的加载
2.1 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
2.2 load_images
上图可见,
load_images
与类加载无关。
2.3 unmap_image
这个也与类加载无关。
最终我们将目标锁定在函数map_images_nolock
。
2.4 map_images_nolock
2.5 _read_images
接着我们来到_read_images
。
通过对
_read_images
源码的大致分析,总共有以下流程:
- 条件控制,首次进来
_read_images
时的一些处理- 修复@selector的混乱问题
- 针对错误和混乱的类的处理
- 修复一些未加载的类
- 修复一些消息
- 加载协议
- 修复未被加载的协议
- 分类的处理
- 类的加载处理
- 被入侵的类的处理
- 实现所有类
- 打印一些信息
3. 与类加载相关的流程
我们对_read_images
函数流程的大致分析,可以发现,discover classes
和realize non-lazy classes
是和类的加载最相关的。
3.1 discover classes
// 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;
}
}
}
ts.log("IMAGE TIMES: discover classes");
很明显,在Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
做了读取类的信息。
3.2 realize non-lazy classes
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &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);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
关键代码是:realizeClassWithoutSwift(cls, nil);
-->实现类的相关操作。
3.3 readClass
再看看
addNamedClass
然后是
addClassTableEntry
3.4 realizeClassWithoutSwift
3.5 methodizeClass
3.6 示例验证以上流程
#import
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;
- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;
+ (void)kc_sayClassMethod;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson.h"
@implementation LGPerson
//+ (void)load{
//
//}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
在main.m中调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person kc_instanceMethod1];
NSLog(@"%p",person);
}
return 0;
}
在readClass
中设置断点
在
realizeClassWithoutSwift
中设置断点
run
运行Demo
根据调用栈信息,
reaizeClassWithoutSwift
的调用时机是在消息发送的慢速查找lookUpImpOrForward
中,
消息发送之前会先判断类是否实现,cls = initializeAndLeaveLocked(cls, inst, runtimeLock)----这是OC中著名的懒加载机制,将类的加载推迟到第一次方法调用的时候。
懒加载的调用栈是:
[LGPerson alloc] --> objc_alloc -->callAlloc --> _objc_msgSend_uncached -->lookUpImpOrForward -->initializeAndLeaveLocked-->initializeAndMaybeRelock-->realizeClassMaybeSwiftAndUnlock-->realizeClassMaybeSwiftMaybeRelock --> realizeClassWithoutSwift
那非懒加载呢?有什么方式可以提前加载LGPerson类?之前我们分析过load方法是优先加载的,我们可以再LGPerson里实现+load方法,再断点看看:
非懒加载调用栈是:
_dyld_start --> _objc_init --> _dyld_objc_notify_register --> dyld::registerObjcNotifiers --> dyld::notityBatchPartial --> map_images -->map_images_nolock --> _read_images --> realizeClassWithoutSwift
总结流程
我们通过对_objc_init
函数流程的分析,找到了_dyld_objc_notify_register
回调函数的注册,然后通过对3个回调函数的大致流程的分析,锁定到类的加载与函数map_images
相关,进而查看其源码,走到了_read_images
,大致分析了_read_images
的流程,其中discover classes
和realize non-lazy classes
均与类的加载有关联,然后分析了readClass
和realizeClassWithoutSwift
的流程,这两个函数就是类的加载
的核心流程,最后我们用例子验证了类的懒加载
与非懒加载
的两种调用栈的情况。