OC底层 - 类的加载(1)
前言
一、environ_init 读取影响运行时的环境变量
二、tls_init 、static_init 、runtime_init
三、exception_init 以及崩溃监听
四、cache_t::init() 和 _imp_implementationWithBlock_init
五、map_images和load_images 调用时机
六、map_images分析
七、readClass分析
经过对应用程序加载的大致分析,基本上了解了app启动的全过程。但是类里面的方法、属性、协议、成员变量是怎么映射进来的呢?包括Class中的ro-> rw ->rwe(为了减少内存开销,将运行时才添加的方法或者属性(关联对象)等保存到rwe) 的数据都是怎么拿到的呢?
那么我们就再继续探索类的整个加载流程。
再探索之前,我们先来分析一下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();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
一、environ_init 读取影响运行时的环境变量
/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
* environ_init
* 读取影响运行时的环境变量。
* 如果需要,还打印环境变量帮助。
**********************************************************************/
void environ_init(void)
{
if (issetugid()) {
// All environment variables are silently ignored when setuid or setgid
// This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
// 当 setuid 或 setgid 时,所有环境变量都会被静默忽略
// 这包括 OBJC_HELP 和 OBJC_PRINT_OPTIONS 本身。
return;
}
bool PrintHelp = true;
bool PrintOptions = true;
bool maybeMallocDebugging = false;
......
// Special case: enable some autorelease pool debugging
// when some malloc debugging is enabled
// and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
......
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp || PrintOptions) {
.....
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}
我们将PrintHelp和PrintOptions设置为true,然后运行,可以看到打印出了很多的环境变量。
简单的说几个环境变量:
1、OBJC_DISABLE_NONPOINTER_ISA 是否关掉非指针类型的isa,也就是设置isa只保存class的地址。
2、OBJC_PRINT_LOAD_METHODS 打印所有实现+(void)load函数的类。
或者在终端输入:export OBJC_HELP=1 命令。
二、tls_init 、static_init 、runtime_init
1、tls_init 关于线程key的绑定
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_create第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
2、static_init
运行当前objc库中的C ++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init(),
因此我们必须自己做
3、runtime_init
void runtime_init(void)
{
//分类处理的初始化
objc::unattachedCategories.init(32);
//初始化存放类的表(储存加载完毕的类)
objc::allocatedClasses.init();
}
三、exception_init 以及崩溃监听
初始化libobjc的异常处理系统
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
* 初始化 libobjc 的异常处理系统。
* 由 map_images() 调用。
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
注意crash并不是崩溃,而是系统发出的一些不允许的规定。
跟着_objc_terminate这个回调函数继续看源码:
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
* 1. 检查是否有活动异常
* 2. 如果是,检查它是否是 Objective-C 异常
* 3. 如果是,则使用该对象调用我们注册的回调。
* 4. 最后,调用前面的终止处理程序。
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
可以看到在catch里面有一个uncaught_handler
/***********************************************************************
* _objc_default_uncaught_exception_handler
* Default uncaught exception handler. Expected to be overridden by Foundation.
* _objc_default_uncaught_exception_handler
* 默认的未捕获异常处理程序。 预计会被基金会覆盖。
**********************************************************************/
static void _objc_default_uncaught_exception_handler(id exception)
{
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;
uncaught_handler默认为_objc_default_uncaught_exception_handler,我们可以在上层这设置这个回调函数,从而达到检测系统崩溃的目的。
/***********************************************************************
* objc_setUncaughtExceptionHandler
* Set a handler for uncaught Objective-C exceptions.
* Returns the previous handler.
* objc_setUncaughtExceptionHandler
* 为未捕获的 Objective-C 异常设置处理程序。
* 返回前一个处理程序。
**********************************************************************/
objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
通过objc_setUncaughtExceptionHandler可以设置。

简单的来说就是:由于上层代码的问题(比如数据越界,野指针等),系统发出了不允许的指令,紧接着会抛出异常,如果上层设置了异常回调函数,在抛出异常的时候,会执行这个回调函数,最后调用终止程序。
知道了底层的原理之后,我们在上层做一个监听:
#import "MyUncaughExceptionHandle.h"
@implementation MyUncaughExceptionHandle
void MyExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
//在这里获取堆栈信息等
}
+ (void)inistallUncaughtSinalExceptionHandler {
//NSSetUncaughtExceptionHandler是objc_setUncaughtExceptionHandler的上层封装
NSSetUncaughtExceptionHandler(&MyExceptionHandlers);
}
关于崩溃的监听流程以及上报服务器,后面我会自己具体的去做一个工具。
四、cache_t::init() 和 _imp_implementationWithBlock_init
cache_t::init() 就是缓存条件的初始化
_imp_implementationWithBlock_init 就是启动回调机制。通常这不会做什么,因为所有的初始化都
是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
五、map_images和load_images 调用时机
先回顾,在dyld加载的过程中,为什么会来到_objc_init。因为_objc_init是runtime,运行时的加载,肯定会加载类的信息。
核心代码:
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
_objc_init中确实向dyld注册了map_images和load_images,那么map_images和load_images它们是什么时候被调用的呢?
我们都知道,我们写的代码会通过编译->汇编>链接,最终生成可执行文件。但是当我运行一个app的时候,首先应该把可执行文件的信息加载映射到内存里面,我们才可以去使用。
map_images 是管理文件中和动态库中所有的符号,而load_images之前分析过是加载执行load方法。
那么回到最开始我提的问题,类的信息是如何被加载的呢?这个就和map_images有关系了。
我们先来分析一下map_images和load_images是什么时候被调用的。
_dyld_objc_notify_register的注册函数中,map_images传入的是指针,load_images传入的是函数。
例如
void test() {
}
int main(int argc, char *argv[])
{
printf("0x%x\n",test);
printf("0x%x\n",&test);
}
//因此,对于test和&test你应该这样理解,test是函数的首地址,它的类型是void (),&test表示一个指向函数test这个对象的地址,
//它的类型是void (*)(),因此test和&test所代表的地址值是一样的,但类型不一样。test是一个函数,&test表达式的值是一个指针!
因为map_images 会在下层改变的时候改变,load_images只是去调用load方法,所有一个是指针(引用类型),一个是函数(值类型)。
例子:
@implementation ViewController
//定义函数指针
//函数返回值类型 (* 指针变量名) (函数参数列表);
typedef void (*_dyld_test_objc_notify_init)();
//生命函数指针变量
static _dyld_test_objc_notify_init test_init;
void test() {
printf("输出");
}
void registerMethod(_dyld_test_objc_notify_init init) {
test_init = init;
//函数名就是地址,而函数名对应的地址里存放的内容仍然是函数的地址,所以无论取几次星号,结果都一样。
(*test_init)();
}
- (void)viewDidLoad {
[super viewDidLoad];
registerMethod(test);
}
@end
接下来进入dyld的源码,跨库调用了 _dyld_objc_notify_register
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
接着进入registerObjCNotifiers,发现将objc中的map_images、load_images、unmapped都赋值给了dyld中的sNotifyObjCMapped、sNotifyObjCInit、sNotifyObjCUnmapped
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);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
那么我们全局搜索一下sNotifyObjCMapped、sNotifyObjCInit、sNotifyObjCUnmapped 就知道他们在哪里调用了。sNotifyObjCInit 之前分析过了,在镜像文件初始化完成之后会去调用,sNotifyObjCUnmapped是应用程序结束的时候,dyld的卸载释放相关,这里不做重点分析。
我们重点来分析一下sNotifyObjCMapped在哪里调用。继续看源码:
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification) {
...
// tell objc about new images
if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
.....
if ( objcImageCount != 0 ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
uint64_t t0 = mach_absolute_time();
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
uint64_t t1 = mach_absolute_time();
ImageLoader::fgTotalObjCSetupTime += (t1-t0);
}
}
...
}
全局搜索一下,看到只有在notifyBatchPartial函数中调用,那么registerObjCNotifiers中就有notifyBatchPartial的调用。当然还有其他地方调用,就不一一去分析了。
所以,在注册回调函数的同时,就会去调用map_images。
六、map_images分析
进入map_images
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,看重点代码:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
//局部处理一些事件
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable's size
// If dyld3 optimized the main executable, then there shouldn't
// be any selrefs needed in the dynamic map so we can just init
// to a 0 sized map
if ( !hi->hasPreoptimizedSelectors() ) {
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
}
hList[hCount++] = hi;
if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname(),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "",
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
hi->info()->optimizedByDyld()?" (preoptimized)":"");
}
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
// 执行一次性运行时初始化,必须推迟到
// 找到可执行文件本身。 这需要在
// 进一步初始化之前完成。
//(如果可执行文件不包含Objective-C 代码但Objective-C
// 稍后动态加载,则该信息列表中可能不存在该可执行文件。
if (firstTime) {
sel_init(selrefCount);
arr_init();
}
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// Call image load funcs after everything is set up.
//一切设置完毕后调用加载函数。
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
}
其中核心的函数就是_read_images,读取镜像文件。
继续来看_read_images的核心方法:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//1.条件控制进行一次的加载
.....
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break;
}
}
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
//小对象处理
initializeTaggedPointerObfuscator();
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
//设置hash表,存放类
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
//2.修复预编译阶段的'@selector'的混乱问题
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) 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 sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
//3.错误混乱的类处理
// 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;
}
//GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist");
//从macho里面读取来classlist
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");
//4.修复重映射一些没有被镜像文件加载进来的类
// 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) {
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]);
}
}
}
ts.log("IMAGE TIMES: remap classes");
//5.修复一些消息
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
//修复旧的 objc_msgSend_fixup 调用站点
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
//6.当我们类里面有协议的时候:readProtocol
// Discover protocols. Fix up protocol refs.
//发现协议。 修复协议参考。
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();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// Note, after launch we do need to walk the protocol as the protocol
// in the shared cache is marked with isCanonical() and that may not
// be true if some non-shared cache binary was chosen as the canonical
// definition
if (launchTime && isPreoptimized) {
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);
}
}
ts.log("IMAGE TIMES: discover protocols");
//7.修复没有被加载的协议
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
// 修正@protocol 引用 // 预先优化的图像可能已经有正确的 // 答案,但我们不确定。
for (EACH_HEADER) {
// At launch time, we know preoptimized image refs are pointing at the
// shared cache definition of a protocol. We can skip the check on
// launch, but have to visit @protocol refs for shared cache images
// loaded later.
if (launchTime && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
ts.log("IMAGE TIMES: fix up @protocol references");
//8.分类处理
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
// 发现类别。 仅在初始类别
// 附件完成后执行此操作。 对于启动时出现的类别,
// 发现被推迟到
// _dyld_objc_notify_register 调用完成后的第一个 load_images 调用。 rdar://问题/53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
ts.log("IMAGE TIMES: discover categories");
//9.类的加载处理
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// 类别发现必须延迟以避免潜在的竞争
// 当其他线程在此线程完成其修复之前调用新类别代码时。
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&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");
//10.没有被处理的类,优化防止被侵犯
//实现新解析的未来类,以防 CF 操作它们
// Realize newly-resolved future classes, in case CF manipulates them
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);
}
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// Print preoptimization statistics
//打印预优化统计信息
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
.....
}
#undef EACH_HEADER
}
通过read_image的函数,可以大致的总结做的事情如下:
1、条件控制进行一次的加载
可以看到会进行对小对象的处理initializeTaggedPointerObfuscator。
然后会创建一张hash表,用来存放类的名称等信息。
负载因子 = 填入表中元素的个数 / 散列表的长度
//负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
2、修复预编译阶段的'@selector'的混乱问题
由于方法函数都是从macho中读取的,每一个库中的sel虽然名字相同,但是地址是不同的,最终要全部加载到内容中时需要修复一致性的问题。

比如retain方法,sel的名字是一样的,但是地址不一样,也就证明了,sel是带地址的字符串。
3、错误混乱的类处理
处理类的信息发生的混乱的情况,但是很少会发生。这里注意,如果类发生混乱,是直接被删除然后重新创建,但不是被移动,因为如果移动的话,性能损耗要大。
在这步处理的过程中,有一个readClass
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;
}
}
我们打印一下cls和newCls

可以看到,经过readClass之后,cls的地址就和类进行了绑定,所以readClass后面我们具体分析。
4、修复重映射一些没有被镜像文件加载进来的类
一般很少会发生,暂时不进行研究。
5、修复一些消息
一般也很少发生,暂时不进行研究。
6、当我们类里面有协议的时候:readProtocol
对协议的处理,后面再具体分析
7、修复没有被加载的协议
搭配协议的处理,后面再具体分析
8、分类处理(已经被推迟到load_image中)
可以看到代码的注释,分类处理被推迟到_dyld_objc_notify_register 调用完成后的第一个 load_images 调用。
9、类的加载处理
只处理非懒加载类。后面会具体的分析什么是懒加载和非懒加载类。
10、没有被处理的类,优化防止被侵犯
一般也很少发生,暂时不进行研究。
七、readClass分析
/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be:
* - cls
* - nil (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
* readClass * 读取由编译器编写的类和元类。
* 返回新的类指针。 这可能是: * - cls
* - nil(cls 缺少弱链接超类)
* - 其他(此类的空间由未来的类保留)
* * 请注意,此函数执行的所有工作都由 * 预检 mustReadClasses()。 不要在不更新该功能的情况下更改此功能。
* * 锁定:map_images 或 objc_readClassPair 获取的 runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
const char *myclassNme = "MyTestObjc";
if (strcmp(mangledName, myclassNme) == 0) {
printf("===");
}
if (missingWeakSuperclass(cls)) {
.............
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) {
//可能被未来处理的
..........................
}
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
首先读取类的名称,然后核心方法执行addNamedClass:
/***********************************************************************
* 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
* 将 name => cls 添加到命名的非元类映射。
* 警告重复的类名并保留旧的映射。
* 锁定:runtimeLock 必须由调用者持有
**********************************************************************/
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,添加类到所有类的表中
/***********************************************************************
* 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.
* 将一个类添加到所有类的表中。 如果 addMeta 为 true,
* 也会自动添加类的元类。 * 锁定:runtimeLock 必须由调用者持有。
**********************************************************************/
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.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
而这张类的表是在runtime_init的时候创建的:
void runtime_init(void)
{
//分类处理的初始化
objc::unattachedCategories.init(32);
//初始化存放类的表(储存加载完毕的类)
objc::allocatedClasses.init();
}
整个read_class的流程就是从macho中将类的信息(__objc_classlist)读到内存(ClassTable)中来。