1. 回顾

在前两篇博文中,已经对dyld动态链接器的底层源码进行了探索分析,但是dyld链接images镜像文件到内存的过程我们还不知道,接下来的几篇博文就着重去探索。

iOS底层探索之dyld(上):动态链接器流程分析

iOS底层探索之dyld(下):动态链接器流程源码分析

_objc_init方法向dyld中注册了回调函数,下面就补充一点内容,探究下_objc_init方法。

2. _objc_init简单分析

先来看看_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() : 读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助。
  • tls_init():关于线程key的绑定 - ⽐如每线程数据的析构函数
  • static_init() :运⾏C ++静态构造函数。在dyld调⽤我们的静态构造函数之前,libc 会调⽤_objc_init(),因此我们必须⾃⼰做
  • lock_init(): 没有重写,采⽤C++的特性
  • exception_init () 初始化libobjc的异常处理系统
  • cache_init(): 缓存条件初始化
  • runtime_init() : runtime运⾏时环境初始化,⾥⾯主要
    是:unattachedCategories,allocatedClasses 后⾯会分析
  • _imp_implementationWithBlock_init :启动回调机制。通常这不会做什么,因为所有的初始化都
    是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib

2.1 environ_init

environ_init()主要代码如下,在PrintHelp或者PrintOptions条件下,可以在控制台打印输出所有的环境变量。

 void  environ_init(void) {    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.if(PrintHelp || PrintOptions) { ...if  (PrintOptions) {_objc_inform("OBJC_PRINT_OPTIONS is set");}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);}}
}

那么现在把if(PrintHelp)if(PrintOptions && *opt->var)判断条件都去掉,看看控制台打印了哪些东西。

for(size_t i = 0; i <sizeof(Settings)/sizeof(Settings[0]); i++) {const option_t *opt = &Settings[i];_objc_inform("%s: %s", opt->env, opt->help);_objc_inform("%s is set", opt->env);}

打印如果结果如下:

objc[2964]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[2964]: OBJC_PRINT_IMAGES is set
objc[2964]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[2964]: OBJC_PRINT_IMAGE_TIMES is set
objc[2964]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[2964]: OBJC_PRINT_LOAD_METHODS is set
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS is set
。。。。。。省略。。。。。。
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA is set
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
objc[2964]: OBJC_DISABLE_FAULTS: disable os faults
objc[2964]: OBJC_DISABLE_FAULTS is set
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set

这么多环境变量,看着就晕,有没有我们熟悉的啊???

有比如:

  • OBJC_PRINT_IMAGES打印镜像文件
  • OBJC_DISABLE_NONPOINTER_ISA判断是否是优化的指针
  • OBJC_PRINT_LOAD_METHODS打印出程序中所有的load方法等等。

那么我们没有objc的源码该如何查看环境变量呢?
可以使用终端命令export OBJC_HELP=1打印如下

2.1.1 终端命令查看环境变量

2.1.2 Xcode工程设置环境变量

Xcode中环境变量配置的位置:选中运行的target--> Edit scheme... --> Run --> Arguments --> Environment Variables

2.1.2.1 OBJC_DISABLE_NONPOINTER_ISA

设置环境变量OBJC_DISABLE_NONPOINTER_ISA表示是否开启ISA指针优化。YES表示纯指针,NO表示优化后的指针就是nonpointer isa
在iOS底层探索之对象的本质和类的关联特性initIsa(下)博客中介绍过了ISA

首先不设置环境变量看下nonpointer isa

isa低位0号位是1,表示是优化后的isa,而且高位上也有其他的数据

将环境变量OBJC_DISABLE_NONPOINTER_ISA = YES设置为YES 再看看isa是怎么样的。

isa低位0号位是0,表示是isa是纯指针,而且高位除了cls也没有其他的数据了。

2.1.2.2 OBJC_PRINT_LOAD_METHODS

环境变量OBJC_PRINT_LOAD_METHODS打印出程序中所有的load方法,在自定义类中添加load方法,配置环境变量OBJC_PRINT_LOAD_METHODS = YES

+[JPStudent load]这个是我们自定义JPStudent类中的load方法,其它的都是系统级别的load方法。load方法太多会导致你应用程序启动变慢,或者有的人在load方法里面做一些隐藏的操作,那么就可以通过这个环境变量检查出哪个类里面有实现的load方法。

2.2 tls_init

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
}

2.3 static_init

全局静态C++函数,在dyld调用我们的静态构造函数之前,libobjc会调用_objc_init,会先调用自己的C++构造函数,简单说的就是libobjc会调用自己的全局的C++函数,因为这个全局的构函数是个非常重要的函数,为了及时性,就自己先调用起来,从底层源码的注释也可以知道,先调用自己的全局静态C++函数再走dyld

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{size_t count;auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);for(size_t i = 0; i < count; i++) {inits[i]();}auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);for(size_t i = 0; i < count; i++) {UnsignedInitializer init(offsets[i]);init();}
}

2.4 runtime_init

runtime运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表的初始化。

void runtime_init(void)
{  objc::unattachedCategories.init(32);//分类表的初始化objc::allocatedClasses.init();//类表的初始化
}

2.5 exception_init

初始化libobjc库的异常处理,这个和objcdyld中注册回调差不多的意思,就是让你去处理异常的。

 void  exception_init(void){old_terminate = std::set_terminate(&_objc_terminate);}

当你的代码出现crashcrash并不是代码错误,不一定会奔溃。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.
**********************************************************************/
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)();}}
}

_objc_terminate方法里面发现了(*uncaught_handler)((id)e)它会把异常抛出去,全局搜索uncaught_handler

objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{objc_uncaught_exception_handler result = uncaught_handler;uncaught_handler = fn;return result;
}

uncaught_handler = fn告诉程序员可以自己传一个函数的句柄,fn可以是程序员自己定义的函数,然后回调时可以自己处理程序抛出的异常的信息。

2.6 cache_t::init

缓存条件的初始化

void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGESmach_msg_type_number_t count = 0;kern_return_t kr;while (objc_restartableRanges[count].location) {count++;}//开启缓存kr = task_restartable_ranges_register(mach_task_self(),objc_restartableRanges, count)if (kr == KERN_SUCCESS) return;_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

2.7 _imp_implementationWithBlock_init

这个是启动回调机制,通常不会做什么。因为所有的初始化都是惰性的,但是对于某些进程会迫不及待的加载trampolines dylib_imp_implementationWithBlock_init源码如下:

void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX// Eagerly load libobjc-trampolines.dylib in certain processes. Some// programs (most notably QtWebEngineProcess used by older versions of// embedded Chromium) enable a highly restrictive sandbox profile which// blocks access to that dylib. If anything calls// imp_implementationWithBlock (as AppKit has started doing) then we'll// crash trying to load it. Loading it here sets it up before the sandbox// profile is enabled and blocks it.//// This fixes EA Origin (rdar://problem/50813789)// and Steam (rdar://problem/55286131)if (__progname &&(strcmp(__progname, "QtWebEngineProcess") == 0 ||strcmp(__progname, "Steam Helper") == 0)) {Trampolines.Initialize();}
#endif
}

2.8 _dyld_objc_notify_register

_dyld_objc_notify_registerdyld注册回调,这个方法非常重要!!!

  • _dyld_objc_notify_register源码实现如下:
// _dyld_objc_notify_init
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
}
// 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());}}
}

其实在iOS底层探索之dyld(下):动态链接器流程源码分析 博客中也已经介绍了,那么我们再来回顾下。

  • &map_imagesdyldimage镜像文件加载到内存中会调用该函数。
  • load_imagesdyld初始化所有的image镜像文件文件会调用。
  • unmap_image:将image镜像文件移除时会调用。

load_images方法其实就是调用load方法,map_image方法,&map_images是指针传递,指向是同一块实现的地址,如果有什么变化就可以第一时间知道。在dyldsNotifyObjCMapped调用的地方是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers中调用,在objc初始化注册通知时就调用了,所以是先调用map_images后调用load_images这么个函数执行顺序。

那么接下就去看看map_images

3. map_images

3.1 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);}

3.2 map_images_nolock

  • 进入OBJC_PRINT_LOAD_METHODS源码

我们要找的无非就是镜像文件的加载和映射相关的,其他的代码就不用看了,直接折叠起来,去重点代码里面看看。

3.3 read_images

查看_read_images方法,发现代码太多了,无从下手,只能从全局来分析了,于是又把代码折叠起来。

void _read_images(header_info **hList, uint32_t hCount, int
totalClasses, int
unoptimizedTotalClasses)
{... //表示省略部分代码
#define EACH_HEADER \hIndex = 0;\hIndex < hCount && (hi = hList[hIndex]);\hIndex++// 条件控制进行一次的加载if (!doneOnce) { ... }// 修复预编译阶段的`@selector`的混乱的问题// 就是不同类中有相同的方法 但是相同的方法地址是不一样的// Fix up @selector referencesstatic size_t UnfixedSelectors;{ ... }ts.log("IMAGE TIMES: fix up selector references");// 错误混乱的类处理// Discover classes. Fix up unresolved future classes. Mark bundle classes.bool hasDyldRoots = dyld_shared_cache_some_image_overridden();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()) { ... }ts.log("IMAGE TIMES: remap classes");#if SUPPORT_FIXUP// 修复一些消息// Fix up old objc_msgSend_fixup call sitesfor (EACH_HEADER) { ... }ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");#endif// 当类中有协议时:`readProtocol`// 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");// 分类的处理// 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.if (didInitialAttachCategories) { ... }ts.log("IMAGE TIMES: discover categories");// 类的加载处理// Category discovery MUST BE Late to avoid potential races// when other threads call the new category code befor// this thread finishes its fixups.// +load handled by prepare_load_methods()// Realize non-lazy classes (for +load methods and static instances)for (EACH_HEADER) { ... }ts.log("IMAGE TIMES: realize non-lazy classes");// 没有被处理的类,优化那些被侵犯的类// Realize newly-resolved future classes, in case CF manipulates themif (resolvedFutureClasses) { ... }ts.log("IMAGE TIMES: realize future classes");...
#undef EACH_HEADER}

从大体上可以看出是对一些log日志的打印输出,主要如下:

  • 条件控制进行一次加载
  • 修复预编译阶段的@selector的混乱的问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当类中有协议时:readProtocol
  • 修复没有被加载的协议
  • 分类的处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类

下面就重点分析几个比较主要的;

3.3.1 doneOnce

  • doneOnce
 if(!doneOnce) {doneOnce = YES; // 加载一次后,就不会在进判断 doneOnce = YESlaunchTime = YES;...// Preoptimized classes don't go in this table.// 4/3 is NXMapTable's load factorint namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;  //创建哈希表 存放所有的类gdb_objc_realized_classes =NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);ts.log("IMAGE TIMES: first time tasks");}

加载一次下次就不会再次进入判断。第一次进来主要创建表gdb_objc_realized_classes,表里存放所有的类不管是实现的还是未实现的都存放在里面,是一张总表。

3.3.1 UnfixedSelectors

  • UnfixedSelectors
static size_t UnfixedSelectors;
{mutex_locker_t lock(selLock);for (EACH_HEADER) {if (hi->hasPreoptimizedSelectors()) continue;bool isBundle = hi->isBundle();// 从macho文件中获取方法名列表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);//从dyld里面获取方法名称if (sels[i] != sel) {sels[i] = sel;}}}
}

不同类中可能存在相同的方法,但是相同的方法地址是不同的.

sels[i]_getObjc2SelectorRefs是从MachO里面获取的,MachO有相对位移地址和偏移地址,selsel_registerNameNoLockdyld里面获取,dyld是链接整个程序的,所以以dyld的为准。因为方法是存放在类中,每个类中的位置是不一样的,所以方法的地址也就不一样,那么就必须对那些混乱的方法进行修复处理。

for (EACH_HEADER) {if (! mustReadClasses(hi, hasDyldRoots)) {// Image is sufficiently optimized that we need not call readClass()continue;}//从macho中读取类列表信息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;}}
}

cls指向的是一块内存地址,newCls此时还没有赋值,但是系统会随机给newCls分配一块脏地址,断点向下走到if判断处,再次控制台lldb调试看看赋值过后是什么值:


从以上验证可以看出readClass方法的作用:就是把类名和地址关联起来,那么现在去源码看看

3.3.3 readClass

  Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{// 获取类名const char *mangledName = cls->nonlazyMangledName();if (missingWeakSuperclass(cls)) { ... }cls->fixupBackwardDeployingStableSwift();Class replacing = nil;if (mangledName != nullptr) { ... }if (headerIsPreoptimized  &&  !replacing) {...} else {if (mangledName) { //some Swift generic classes can lazily generate their names//将类名和地址关联起来addNamedClass(cls, mangledName, replacing);} else { ...}//将关联的类插入到另一张哈希表中addClassTableEntry(cls);}// for future reference: shared cache never contains MH_BUNDLEsif (headerIsBundle) { ... }return cls;}
  • nonlazyMangledName获取类名。
  • addNamedClass将类名和地址关联绑定起来。
  • addClassTableEntry将关联的类插入到哈希表中,这张表中都是初始化过的类。

为了便于研究,我们可以对cls进行过滤,过滤出我们要研究的类JPStudent

在此流程中会通过cls->nonlazyMangledName()获取类的名称,nonlazyMangledName源码实现如下:

  • nonlazyMangledName
// Get the class's mangled name, or NULL if the class has a lazy// name that hasn't been created yet.const char *nonlazyMangledName() const {return bits.safe_ro()->getName();}
  • safe_ro
// Get the class's ro data, even in the presence of concurrent realization.// fixme this isn't really safe without a compiler barrier at least// and probably a memory barrier when realizeClass changes the data fieldconst class_ro_t *safe_ro() const {class_rw_t *maybe_rw = data();if (maybe_rw->flags & RW_REALIZED) {// maybe_rw is rwreturn maybe_rw->ro();} else {// maybe_rw is actually roreturn (class_ro_t *)maybe_rw;}}
  • addNamedClass 将类名和地址关联绑定起来
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());
}

通过NXMapInsert方法更新gdb_objc_realized_classes哈希表,keynamevaluecls

  • NXMapInsert 实现部分代码
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {MapPair    *pairs = (MapPair *)table->buckets;unsigned  index = bucketOf(table, key);MapPair    *pair = pairs + index;if (key == NX_MAPNOTAKEY) {_objc_inform("*** NXMapInsert: invalid key: -1\n");return NULL;}unsigned numBuckets = table->nbBucketsMinusOne + 1;if (pair->key == NX_MAPNOTAKEY) {pair->key = key; pair->value = value;table->count++;if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);return NULL;}
..... 省略代码  ......
}
  • addClassTableEntry
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.// allocatedClassesauto &set = objc::allocatedClasses.get();ASSERT(set.find(cls) == set.end());if (!isKnownClass(cls))set.insert(cls);if (addMeta)//将元类插入哈希表中addClassTableEntry(cls->ISA(), false);
}

allocatedClasses_objc_initruntime_init运行时环境初始化,主要是unattachedCategoriesallocatedClasses两张表,此时addClassTableEntry的操作是插入allocatedClasses表中。与此同时还对元类进行了相应的处理,在处理类的时候,元类也得处理。

通过源码分析、断点调试,发现对rwro的获取和赋值操作,并不在readClass里面,于是又回到_read_images去看看。

_read_images方法进行,断点跟踪,一步一步走,发现最后走到了realizeClassWithoutSwift处,那么进去看看。

  • realizeClassWithoutSwift 源码如下:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{runtimeLock.assertLocked();class_rw_t *rw;Class supercls;Class metacls;if (!cls) return nil;if (cls->isRealized()) {validateAlreadyRealizedClass(cls);return cls;}ASSERT(cls == remapClass(cls));..... 省略代码...........
}

好家伙,原来对rwro的获取和赋值操作是在realizeClassWithoutSwift里面进行了处理,从dyld_objc_init再到read_images这整个流程就串联起来了,接下来的几篇博客将对类的加载底层原理进行探索分析!

请持续关注!敬请期待!

4.总结

  • 环境变量可以通过Xcode设置和终端命令export OBJC_HELP=1打印查看
  • _read_images是对一些log信息的打印
  • readClass把类名和地址关联起来
  • rw的赋值和ro的获取并不在readClass里面
  • _dyld_objc_notify_register 执行流程图

    更多内容持续更新

iOS底层探索之类的加载(一):read_images分析相关推荐

  1. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  2. iOS开发UI篇—懒加载

    iOS开发UI篇-懒加载 1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...

  3. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  4. [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处?

    [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处? <link rel="apple-touch-startup-image" href=&q ...

  5. 微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题

    微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 参考文章: (1)微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 (2)https://www.cnblogs.com/co ...

  6. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  7. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  8. ios wkwebview弹框_iOS 加载WKWebView

    WKWebView是苹果在iOS 8之后推出的框架WebKit中的浏览器控件, 其加载速度比UIWebView快了许多, 但内存占用率却下降很多, 也解决了加载网页时的内存泄露问题. WKWebVie ...

  9. 模块加载过程代码分析1

    一.概述 模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中.对于每个模块,系统都要分配一个包含以下数据结构的内存区. 一个module对象,表示模块名的一个以null ...

  10. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 )

    文章目录 前言 一.RawDexFile.cpp 中 dvmRawDexFileOpen() 方法分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLoader 加载 ...

最新文章

  1. 《强化学习周刊》第13期:强化学习应用之金融
  2. Joomla 2.5 中文语言包安装模板报错
  3. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 中根据 File 加载 DexFile | loadDexFile 分析 )
  4. linux——延时任务与定时任务
  5. ES分布式机制的透明性,垂直扩容和水平扩容,数据rebalance,master节点,节点平等的分布式架构,shard和replica机制(学习)
  6. android--------Popupwindow的使用
  7. 使用百度链的智能合约来落地公司业务场景
  8. javascript下的数值型比较真的没有那么简单
  9. 从尿检取中段谈数据库压测
  10. 如何下载vue.js
  11. python deap_Python遗传算法框架DEAP-Creating Types
  12. VS2013密钥 VS2013专业版密钥 VS2013旗舰版密钥
  13. 小米无线wifi代理服务器,小米路由器Mini无线中继(桥接)设置教程
  14. .NET 通过Word模板,使用AsposeWord进行数据动态导出Word
  15. PHP解析SOAP生成的xml
  16. 最新,最严谨的手机号校验
  17. 基于Android的看小说APP源码Android本科毕业设计Android小说阅读器、小说APP源码
  18. 如何在macOS 中让Gatekeeper在任何地方允许应用程序
  19. matlab 图形对称,求任意轴对称图形的核心的Matlab代码实现
  20. 打喷嚏 打嗝 打饱嗝 打鼾 打哈切 用日语都怎么说?

热门文章

  1. 201671010140. 2016-2017-2 《Java程序设计》java学习第十六周
  2. sell - 配置service
  3. erlang的timer定时器浅析
  4. 雅虎日本总裁:微软报价忽视了雅虎海外资产
  5. Monad B2 For Dotnet Framework 2.0 RC/RTM Released!
  6. Retrofit的初次使用
  7. Dubbo--002--例子程序
  8. 以太坊智能合约部署与交互
  9. Oct22 实例测试
  10. 用js小类库获取浏览器的高度和宽度信息