《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方法都会被打印
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表示二进制打印
进行环境变量设值后,将nonpointerisa舍弃掉,查看person中的isa的内存地址,说明环境变量的设值生效了
设置环境变量打印load方法,将load方法都打印出来,新增LGPerson的load方法,也会打印。
2.通过终端输出环境变量export OBJC_HELP = 1
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分别用回调函数来接收
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方法什么时候执行呢?
我们在dyld的源码里面搜索sNotifyObjCMapped,找到调用是在notifyBatchPartial方法,继续搜索notifyBatchPartial方法
在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());
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;
}
}
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());
}
这就是为什么前面读到的是地址,后面读到的是类名
拦截特定的类