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
    }

    // <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());
        }
    }
}

1.environ_init():环境变量的初始化,打印下都有哪些环境变量

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


可配置的环境变量@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表示二进制打印


nonpointerisa.png

进行环境变量设值后,将nonpointerisa舍弃掉,查看person中的isa的内存地址,说明环境变量的设值生效了


OBJC_DISABLE_NONPOINTER_ISA@2x.png

非nonpointerisa.png

设置环境变量打印load方法,将load方法都打印出来,新增LGPerson的load方法,也会打印。
OBJC_PRINT_LOAD_METHODS.png
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分别用回调函数来接收

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方法什么时候执行呢?


notifySingle@2x.png

我们在dyld的源码里面搜索sNotifyObjCMapped,找到调用是在notifyBatchPartial方法,继续搜索notifyBatchPartial方法


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());

unmap_image@2x.png

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

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


地址关联类@2x.png

拦截特定的类


LGPerson@2x.png

LGPerson方法实现@2x.png

readClass@2x.png

类从MachO文件中读取到内存中并和类名进行绑定,并添加到已知类的表中

类的加载上.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容