美文网首页iOS学习
iOS-底层原理13-类的加载上

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

作者: 一亩三分甜 | 来源:发表于2020-12-05 20:05 被阅读0次

    《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

    相关文章

      网友评论

        本文标题:iOS-底层原理13-类的加载上

        本文链接:https://www.haomeiwen.com/subject/qmzgwktx.html