iOS-底层原理13-类的加载上

《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理12-应用程序加载》,之后了load_images中调用类,分类中的load方法,本文介绍类中属性,方法,协议,Categories,是如何加载到ro,rw,rwe中的。
由上一篇文章中知道dyld-->libSystem_initializer->libdispatch_init->_os_object_init->_objc_init->回到dyld中的notifySingle->(*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); 从而执行load_images,_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 }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::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()); } } }

1.environ_init():环境变量的初始化,打印下都有哪些环境变量
1.在Xcode可进行环境变量的设置OBJC_DISABLE_NONPOINTER_ISA,设置为YES则去掉为nonpointer的isa指针,另一个环境变量OBJC_PRINT_LOAD_METHODS,设值后,所有load方法都会被打印

iOS-底层原理13-类的加载上
文章图片
可配置的环境变量@2x.png
objc[7503]: OBJC_PRINT_IMAGES: log image and library names as they are loaded objc[7503]: OBJC_PRINT_IMAGES is set objc[7503]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps objc[7503]: OBJC_PRINT_IMAGE_TIMES is set objc[7503]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods objc[7503]: OBJC_PRINT_LOAD_METHODS is set objc[7503]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods objc[7503]: OBJC_PRINT_INITIALIZE_METHODS is set objc[7503]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod: objc[7503]: OBJC_PRINT_RESOLVED_METHODS is set objc[7503]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup objc[7503]: OBJC_PRINT_CLASS_SETUP is set objc[7503]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup objc[7503]: OBJC_PRINT_PROTOCOL_SETUP is set objc[7503]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars objc[7503]: OBJC_PRINT_IVAR_SETUP is set objc[7503]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables objc[7503]: OBJC_PRINT_VTABLE_SETUP is set objc[7503]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods objc[7503]: OBJC_PRINT_VTABLE_IMAGES is set objc[7503]: OBJC_PRINT_CACHE_SETUP: log processing of method caches objc[7503]: OBJC_PRINT_CACHE_SETUP is set objc[7503]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging objc[7503]: OBJC_PRINT_FUTURE_CLASSES is set objc[7503]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache objc[7503]: OBJC_PRINT_PREOPTIMIZATION is set objc[7503]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables objc[7503]: OBJC_PRINT_CXX_CTORS is set objc[7503]: OBJC_PRINT_EXCEPTIONS: log exception handling objc[7503]: OBJC_PRINT_EXCEPTIONS is set objc[7503]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw() objc[7503]: OBJC_PRINT_EXCEPTION_THROW is set objc[7503]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers objc[7503]: OBJC_PRINT_ALT_HANDLERS is set objc[7503]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations objc[7503]: OBJC_PRINT_REPLACED_METHODS is set objc[7503]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions objc[7503]: OBJC_PRINT_DEPRECATION_WARNINGS is set objc[7503]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools objc[7503]: OBJC_PRINT_POOL_HIGHWATER is set objc[7503]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods objc[7503]: OBJC_PRINT_CUSTOM_CORE is set objc[7503]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods objc[7503]: OBJC_PRINT_CUSTOM_RR is set objc[7503]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods objc[7503]: OBJC_PRINT_CUSTOM_AWZ is set objc[7503]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields objc[7503]: OBJC_PRINT_RAW_ISA is set objc[7503]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded objc[7503]: OBJC_DEBUG_UNLOAD is set objc[7503]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses objc[7503]: OBJC_DEBUG_FRAGILE_SUPERCLASSES is set objc[7503]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization objc[7503]: OBJC_DEBUG_NIL_SYNC is set objc[7503]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars objc[7503]: OBJC_DEBUG_NONFRAGILE_IVARS is set objc[7503]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use objc[7503]: OBJC_DEBUG_ALT_HANDLERS is set objc[7503]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak objc[7503]: OBJC_DEBUG_MISSING_POOLS is set objc[7503]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools objc[7503]: OBJC_DEBUG_POOL_ALLOCATION is set objc[7503]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present objc[7503]: OBJC_DEBUG_DUPLICATE_CLASSES is set objc[7503]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing objc[7503]: OBJC_DEBUG_DONT_CRASH is set objc[7503]: OBJC_DISABLE_VTABLES: disable vtable dispatch objc[7503]: OBJC_DISABLE_VTABLES is set objc[7503]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache objc[7503]: OBJC_DISABLE_PREOPTIMIZATION is set objc[7503]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al. objc[7503]: OBJC_DISABLE_TAGGED_POINTERS is set objc[7503]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers objc[7503]: OBJC_DISABLE_TAG_OBFUSCATION is set objc[7503]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields objc[7503]: OBJC_DISABLE_NONPOINTER_ISA is set objc[7503]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork objc[7503]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set

我们先不设置环境变量,查看LGPerson中的isa的内存地址的值,打印出来确实为nonpointisa,最后一个数为1表示nonpointerisa,p/t表示二进制打印

iOS-底层原理13-类的加载上
文章图片
nonpointerisa.png
进行环境变量设值后,将nonpointerisa舍弃掉,查看person中的isa的内存地址,说明环境变量的设值生效了

iOS-底层原理13-类的加载上
文章图片
OBJC_DISABLE_NONPOINTER_ISA@2x.png
iOS-底层原理13-类的加载上
文章图片
非nonpointerisa.png
设置环境变量打印load方法,将load方法都打印出来,新增LGPerson的load方法,也会打印。
iOS-底层原理13-类的加载上
文章图片
OBJC_PRINT_LOAD_METHODS.png iOS-底层原理13-类的加载上
文章图片
LGPersonload方法.png 2.通过终端输出环境变量export OBJC_HELP = 1

export-OBJC_HELP@2x.png 2.tls_init()本地线程池,runloop,autoreleasepool都会依赖于线程,一个初始setter方法,一个析构getter方法
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 }

3.static_init,当前objc-os中的静态构造函数初始化,比dyld的调用还早,在当前dyld调用之前必须有这些静态方法,主要是当前的环境的处理
static void static_init() { size_t count; auto inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } }

4.runtime_init,objc::unattachedCategories.init(32)为了进行分类而进行的初始化,当前初始化class的一张表,用来储存我们加载完毕的类
void runtime_init(void) { objc::unattachedCategories.init(32); objc::allocatedClasses.init(); }

5.exception_init,crash并不是真正的崩溃,系统发的不允许的指令,是违反系统规定,系统给信号Crash,传入发生异常的函数
void exception_init(void) { old_terminate = std::set_terminate(&_objc_terminate); } 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)(); } } } static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;

重写传入捕获异常的方法fn------《iOS-底层原理38-Crash分析》
/*********************************************************************** * objc_setUncaughtExceptionHandler * Set a handler for uncaught Objective-C exceptions. * Returns the previous handler. **********************************************************************/ objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) { objc_uncaught_exception_handler result = uncaught_handler; uncaught_handler = fn; return result; }

6.cache_init()缓存的处理
7._imp_implementationWithBlock_init()实现的处理
回到dyld的执行,_dyld_objc_notify_register(&map_images, load_images, unmap_image)将map_images、load_images、unmap_image传入到dyld中,dyld分别用回调函数来接收
iOS-底层原理13-类的加载上
文章图片
map_images和load_images.png
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; }

dyld的流程接着往下走,dyld中的notifySingle方法调用了*sNotifyObjCInit,从而执行了load_images方法,map_images方法什么时候执行呢?

iOS-底层原理13-类的加载上
文章图片
notifySingle@2x.png
我们在dyld的源码里面搜索sNotifyObjCMapped,找到调用是在notifyBatchPartial方法,继续搜索notifyBatchPartial方法

iOS-底层原理13-类的加载上
文章图片
notifyBatchPartial@2x.png
在notifyBatch方法中调用notifyBatchPartial方法
static void notifyBatch(dyld_image_states state, bool preflightOnly) { notifyBatchPartial(state, false, NULL, preflightOnly, false); }

搜索notifyBatch,在runInitializers调用context.notifyBatch(dyld_image_state_initialized, false),context.notifyBatch调用notifyBatchPartial中调用(*sNotifyObjCMapped)(objcImageCount, paths, mhs); 流程如下
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo) { uint64_t t1 = mach_absolute_time(); mach_port_t thisThread = mach_thread_self(); ImageLoader::UninitedUpwards up; up.count = 1; up.imagesAndPaths[0] = { this, this->getPath() }; processInitializers(context, thisThread, timingInfo, up); context.notifyBatch(dyld_image_state_initialized, false); mach_port_deallocate(mach_task_self(), thisThread); uint64_t t2 = mach_absolute_time(); fgTotalInitTime += (t2 - t1); } static void notifyBatch(dyld_image_states state, bool preflightOnly) { notifyBatchPartial(state, false, NULL, preflightOnly, false); }

程序结束,从dyld链接卸载才会调用unmap_image(*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());

iOS-底层原理13-类的加载上
文章图片
unmap_image@2x.png
iOS-底层原理13-类的加载上
文章图片
main()函数之前类的加载.png 2.map_images和load_images分别做了什么呢?
read_images流程如下:
1: 条件控制进行一次的加载
2: 修复预编译阶段的 @selector 的混乱问题
3: 错误混乱的类处理
4:修复重映射一些没有被镜像文件加载进来的 类
5: 修复一些消息!
6: 当我们类里面有协议的时候 : readProtocol
7: 修复没有被加载的协议
8: 分类处理
9: 类的加载处理
10 : 没有被处理的类 优化那些被侵犯的类
1.小对象指针经过了一层小小的处理,并不是简单的指针,比如int类型,NSNumber类型
static void initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } }

2.创建一些表,类存表,方便查找
// 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);

3.地址统一,在不同的动态库中存在的类的地址不一样,比如[LGPerson load],[LGPerson class]方法在libdyld,libSystem,libDispatch的库,在这些库中所在位置的坐标是不一样的,最后都加载到内存中,要确保类是同一个,地址统一,进行统一调度,sel中不仅存的地址值,也存的类的字符串,发现不一致的进行统一,SEL不是一个简单的字符串,是带地址的字符串
static void initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } }

iOS-底层原理13-类的加载上
文章图片
unfixedSelectors@2x.png 3.readClass,地址赋上名字,MachO文件在MachView中显示会出现很多内存地址,内存地址怎么变成类呢?cls的地址赋上名字,走到cls,readClass精简代码如下: cls是内存地址,通过调用addNamedClass(cls, mangledName, replacing)将类的名字和内存地址进行绑定,存放在刚开始进入read_images方法中时,创建的表gdb_objc_realized_classes中,通过调用addClassTableEntry(cls)将类和类的元类加到要开辟的类的表中,表被初始化在_objc_init() -> runtime_init() -> objc::allocatedClasses.init(); 中,此时这个类在共享缓存中变为已知类。
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); const char *LGPersonName = "LGPerson"; if (strcmp(LGPersonName, mangledName) == 0) { printf("%s -哎唷不错!- %s",__func__,mangledName); } 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); } } 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()); }

这就是为什么前面读到的是地址,后面读到的是类名

iOS-底层原理13-类的加载上
文章图片
地址关联类@2x.png
【iOS-底层原理13-类的加载上】拦截特定的类

iOS-底层原理13-类的加载上
文章图片
LGPerson@2x.png
iOS-底层原理13-类的加载上
文章图片
LGPerson方法实现@2x.png
iOS-底层原理13-类的加载上
文章图片
readClass@2x.png 类从MachO文件中读取到内存中并和类名进行绑定,并添加到已知类的表中 iOS-底层原理13-类的加载上
文章图片
类的加载上.png

    推荐阅读