Runtime加载过程

作者: 刘小壮 | 来源:发表于2018-05-19 18:26 被阅读1677次
    该文章属于<简书 — 刘小壮>原创,转载请注明:

    <简书 — 刘小壮> https://www.jianshu.com/p/4fb2d7014e9e


    博客配图

    程序加载过程

    在iOS程序中会用到很多系统的动态库,这些动态库都是动态加载的。所有iOS程序共用一套系统动态库,在程序开始运行时才会开始链接动态库。

    the dynamic link editor

    除了在项目设置里显式出现的动态库外,还会有一些隐式存在的动态库。例如objcRuntime所属的libobjc.dyldlibSystem.dyld,在libSystem中包含常用的libdispatch(GCD)libsystem_c(C语言基础库)、libsystem_blocks(Block)等。

    使用动态库的优点:

    1. 防止重复。iOS系统中所有App公用一套系统动态库,防止重复的内存占用。
    2. 减少包体积。因为系统动态库被内置到iOS系统中,所以打包时不需要把这部分代码打进去,可以减小包体积。
    3. 动态性。因为系统动态库是动态加载的,所以可以在更新系统后,将动态库换成新的动态库。

    加载过程

    在应用程序启动后,由dyld(the dynamic link editor)进行程序的初始化操作。大概流程就像下面列出的步骤,其中第3、4、5步会执行多次,在ImageLoader加载新的image进内存后就会执行一次。

    1. 在引用程序启动后,由dyld将应用程序加载到二进制中,并完成一些文件的初始化操作。
    2. Runtimedyld中注册回调函数。
    3. 通过ImageLoader将所有image加载到内存中。
    4. dyldimage发生改变时,主动调用回调函数。
    5. Runtime接收到dyld的函数回调,开始执行map_imagesload_images等操作,并回调+load方法。
    6. 调用main()函数,开始执行业务代码。

    ImageLoaderimage的加载器,image可以理解为编译后的二进制。

    下面是在Runtimemap_images函数打断点,观察回调情况的汇编代码。可以看出,调用是由dyld发起的,由ImageLoader通知dyld进行调用。

    汇编调用

    关于dyld我并没有深入研究,有兴趣的同学可以到Github上下载源码研究一下。

    动态加载

    一个OC程序可以在运行过程中动态加载和链接新类或Category,新类或Category会加载到程序中,其处理方式和其他类是相同的。动态加载还可以做许多不同的事,动态加载允许应用程序进行自定义处理。

    OC提供了objc_loadModules运行时函数,执行Mach-O中模块的动态加载,在上层NSBundle对象提供了更简单的访问API

    map images

    Runtime加载时,会调用_objc_init函数,并在内部注册三个函数指针。其中map_images函数是初始化的关键,内部完成了大量Runtime环境的初始化操作。

    map_images函数中,内部也是做了一个调用中转。然后调用到map_images_nolock函数,内部核心就是_read_images函数。

    void _objc_init(void)
    {
        // .... 各种init
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    
    void map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    {
        rwlock_writer_t lock(runtimeLock);
        return map_images_nolock(count, paths, mhdrs);
    }
    
    void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                      const struct mach_header * const mhdrs[])
    {
        if (hCount > 0) {
            _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
        }
    }
    

    _read_images函数中完成了大量的初始化操作,函数内部代码量比较大,下面是精简版带注释的源代码。

    先整体梳理一遍_read_images函数内部的逻辑:

    1. 加载所有类到类的gdb_objc_realized_classes表中。
    2. 对所有类做重映射。
    3. 将所有SEL都注册到namedSelectors表中。
    4. 修复函数指针遗留。
    5. 将所有Protocol都添加到protocol_map表中。
    6. 对所有Protocol做重映射。
    7. 初始化所有非懒加载的类,进行rwro等操作。
    8. 遍历已标记的懒加载的类,并做初始化操作。
    9. 处理所有Category,包括ClassMeta Class
    10. 初始化所有未初始化的类。
    void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
    {
        header_info *hi;
        uint32_t hIndex;
        size_t count;
        size_t i;
        Class *resolvedFutureClasses = nil;
        size_t resolvedFutureClassCount = 0;
        static bool doneOnce;
        TimeLogger ts(PrintImageTimes);
    
    #define EACH_HEADER \
        hIndex = 0;         \
        hIndex < hCount && (hi = hList[hIndex]); \
        hIndex++
    
        if (!doneOnce) {
            doneOnce = YES;
            // 实例化存储类的哈希表,并且根据当前类数量做动态扩容
            int namedClassesSize = 
                (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
            gdb_objc_realized_classes =
                NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        }
    
        // 由编译器读取类列表,并将所有类添加到类的哈希表中,并且标记懒加载的类并初始化内存空间
        for (EACH_HEADER) {
            if (! mustReadClasses(hi)) {
                continue;
            }
    
            bool headerIsBundle = hi->isBundle();
            bool headerIsPreoptimized = hi->isPreoptimized();
    
            /** 将新类添加到哈希表中 */
            
            // 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
            classref_t *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) {
                // 数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及自己创建的类
                Class cls = (Class)classlist[i];
                // 通过readClass函数获取处理后的新类,内部主要操作ro和rw结构体
                Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    
                // 初始化所有懒加载的类需要的内存空间
                if (newCls != cls  &&  newCls) {
                    // 将懒加载的类添加到数组中
                    resolvedFutureClasses = (Class *)
                        realloc(resolvedFutureClasses, 
                                (resolvedFutureClassCount+1) * sizeof(Class));
                    resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
                }
            }
        }
        
        // 将未映射Class和Super Class重映射,被remap的类都是非懒加载的类
        if (!noClassesRemapped()) {
            for (EACH_HEADER) {
                // 重映射Class,注意是从_getObjc2ClassRefs函数中取出类的引用
                Class *classrefs = _getObjc2ClassRefs(hi, &count);
                for (i = 0; i < count; i++) {
                    remapClassRef(&classrefs[i]);
                }
                // 重映射父类
                classrefs = _getObjc2SuperRefs(hi, &count);
                for (i = 0; i < count; i++) {
                    remapClassRef(&classrefs[i]);
                }
            }
        }
    
        // 将所有SEL都注册到哈希表中,是另外一张哈希表
        static size_t UnfixedSelectors;
        sel_lock();
        for (EACH_HEADER) {
            if (hi->isPreoptimized()) continue;
    
            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                // 注册SEL的操作
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    
        // 修复旧的函数指针调用遗留
        for (EACH_HEADER) {
            message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
            if (count == 0) continue;
            for (i = 0; i < count; i++) {
                // 内部将常用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
                fixupMessageRef(refs+i);
            }
        }
    
        // 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
        for (EACH_HEADER) {
            extern objc_class OBJC_CLASS_$_Protocol;
            // cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
            Class cls = (Class)&OBJC_CLASS_$_Protocol;
            assert(cls);
            // 获取protocol哈希表
            NXMapTable *protocol_map = protocols();
            bool isPreoptimized = hi->isPreoptimized();
            bool isBundle = hi->isBundle();
    
            // 从编译器中读取并初始化Protocol
            protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
            for (i = 0; i < count; i++) {
                readProtocol(protolist[i], cls, protocol_map, 
                             isPreoptimized, isBundle);
            }
        }
        
        // 修复协议列表引用,优化后的images可能是正确的,但是并不确定
        for (EACH_HEADER) {
            // 需要注意到是,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不一样
            protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapProtocolRef(&protolist[i]);
            }
        }
    
        // 实现非懒加载的类,对于load方法和静态实例变量
        for (EACH_HEADER) {
            classref_t *classlist = 
                _getObjc2NonlazyClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;
                // 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
                realizeClass(cls);
            }
        }
    
        // 遍历resolvedFutureClasses数组,实现所有懒加载的类
        if (resolvedFutureClasses) {
            for (i = 0; i < resolvedFutureClassCount; i++) {
                // 实现懒加载的类
                realizeClass(resolvedFutureClasses[i]);
                resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
            }
            free(resolvedFutureClasses);
        }
    
        // 发现和处理所有Category
        for (EACH_HEADER) {
            // 外部循环遍历找到当前类,查找类对应的Category数组
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
            // 内部循环遍历当前类的所有Category
            for (i = 0; i < count; i++) {
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
    
                // 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    // 将Category添加到对应Class的value中,value是Class对应的所有category数组
                    addUnattachedCategoryForClass(cat, cls, hi);
                    // 将Category的method、protocol、property添加到Class
                    if (cls->isRealized()) {
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                }
    
                // 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
                // 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
                if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        remethodizeClass(cls->ISA());
                    }
                }
            }
        }
    
        // 初始化从磁盘中加载的所有类,发现Category必须是最后执行的
        // 从runtime objc4-532版本源码来看,DebugNonFragileIvars字段一直是-1,所以不会进入这个方法中
        if (DebugNonFragileIvars) {
            realizeAllClasses();
        }
    #undef EACH_HEADER
    }
    

    其内部还调用了很多其他函数,后面会详细介绍函数内部实现。

    load images

    在项目中经常用到load类方法,load类方法的调用时机比main函数还要靠前。load方法是由系统来调用的,并且在整个程序运行期间,只会调用一次,所以可以在load方法中执行一些只执行一次的操作。

    一般Method Swizzling都会放在load方法中执行,这样在执行main函数前,就可以对类方法进行交换。可以确保正式执行代码时,方法肯定是被交换过的。

    如果对一个类添加Category后,并且重写其原有方法,这样会导致Category中的方法覆盖原类的方法。但是load方法却是例外,所有Category和原类的load方法都会被执行。

    源码分析

    load方法由Runtime进行调用,下面我们分析一下load方法的实现,load的实现源码都在objc-loadmethod.mm中。

    在一个新的工程中,我们创建一个TestObject类,并在其load方法中打一个断点,看一下系统的调用堆栈。

    调用堆栈

    从调用栈可以看出,是通过系统的动态链接器dyld开始的调用,然后调到Runtimeload_images函数中。load_images函数是通过_dyld_objc_notify_register函数,将自己的函数指针注册给dyld的。

    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();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    load_images函数中主要做了两件事,首先通过prepare_load_methods函数准备Class load listCategory load list,然后通过call_load_methods函数调用已经准备好的两个方法列表。

    void
    load_images(const char *path __unused, const struct mach_header *mh)
    {
        if (!hasLoadMethods((const headerType *)mh)) return;
        prepare_load_methods((const headerType *)mh);
        call_load_methods();
    }
    

    首先我们看一下prepare_load_methods函数的实现,看一下其内部是怎么查找load方法的。可以看到,其内部主要分为两部分,查找Classload方法列表和查找Category方法列表。

    准备类的方法列表时,首先通过_getObjc2NonlazyClassList函数获取所有非懒加载类的列表,这时候获取到的是一个classref_t类型的数组,然后遍历数组添加load方法列表。

    Category过程也是类似,通过_getObjc2NonlazyCategoryList函数获取所有非懒加载Category的列表,得到一个category_t类型的数组,需要注意的是这是一个指向指针的指针。然后对其进行遍历并添加到load方法列表,ClassCategoryload方法列表是两个列表。

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        // 获取到非懒加载的类的列表
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            // 设置Class的调用列表
            schedule_class_load(remapClass(classlist[i]));
        }
        
        // 获取到非懒加载的Category列表
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            // 忽略弱链接的类别
            if (!cls) continue;
            // 实例化所属的类
            realizeClass(cls);
            // 设置Category的调用列表
            add_category_to_loadable_list(cat);
        }
    }
    

    在添加类的load方法列表时,内部会递归遍历把所有父类的load方法都添加进去,顺着继承者链的顺序添加,会先把父类添加在前面。然后会调用add_class_to_loadable_list函数,将自己的load方法添加到对应的数组中。

    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        // 已经添加Class的load方法到调用列表中
        if (cls->data()->flags & RW_LOADED) return;
    
        // 确保super已经被添加到load列表中,默认是整个继承者链的顺序
        schedule_class_load(cls->superclass);
        
        // 将IMP和Class添加到调用列表
        add_class_to_loadable_list(cls);
        // 设置Class的flags,表示已经添加Class到调用列表中
        cls->setInfo(RW_LOADED); 
    }
    

    Category则不需要考虑父类的问题,所以直接在prepare_load_methods函数中遍历Category数组,然后调用add_category_to_loadable_list函数即可。

    add_category_to_loadable_list函数中,会判断当前Category有没有实现load方法,如果没有则直接return,如果实现了则添加到loadable_categories数组中。

    类的add_class_to_loadable_list函数内部实现也是类似,区别在于类的数组叫做loadable_classes

    void add_category_to_loadable_list(Category cat)
    {
        IMP method;
    
        // 获取Category的load方法的IMP
        method = _category_getLoadMethod(cat);
    
        // 如果Category没有load方法则return
        if (!method) return;
        // 如果已使用大小等于数组大小,对数组进行动态扩容
        if (loadable_categories_used == loadable_categories_allocated) {
            loadable_categories_allocated = loadable_categories_allocated*2 + 16;
            loadable_categories = (struct loadable_category *)
                realloc(loadable_categories,
                                  loadable_categories_allocated *
                                  sizeof(struct loadable_category));
        }
    
        loadable_categories[loadable_categories_used].cat = cat;
        loadable_categories[loadable_categories_used].method = method;
        loadable_categories_used++;
    }
    

    到此为止,loadable_classesloadable_categories两个数组已经准备好了,load_images会调用call_load_methods函数执行这些load方法。在这个方法中,call_class_loads函数是负责调用类方法列表的,call_category_loads负责调用Category的方法列表。

    void call_load_methods(void)
    {
        bool more_categories;
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 反复执行call_class_loads函数,直到数组中没有可执行的load方法
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
            more_categories = call_category_loads();
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
        loading = NO;
    }
    

    下面是调用类方法列表的代码,内部主要是通过对loadable_classes数组进行遍历,并获取到loadable_class的结构体,结构体中存在ClassIMP,然后直接调用即可。

    Category的调用方式和类的一样,就不在下面贴代码了。需要注意的是,load方法都是直接调用的,并没有走运行时的objc_msgSend函数。

    static void call_class_loads(void)
    {
        int i;
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        for (i = 0; i < used; i++) {
            Class cls = classes[i].cls;
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue; 
            (*load_method)(cls, SEL_load);
        }
        
        if (classes) free(classes);
    }
    
    struct loadable_class {
        Class cls;  // may be nil
        IMP method;
    };
    

    根据上面的源码分析,我们可以看出load方法的调用顺序应该是“父类 -> 子类 -> 分类”的顺序。因为执行加载Class的时机是在Category之前的,而且load子类之前会先load父类,所以是这种顺序。

    initialize

    load方法类似的也有initialize方法,initialize方法也是由Runtime进行调用的,自己不可以直接调用。与load方法不同的是,initialize方法是在第一次调用类所属的方法时,才会调用initialize方法,而load方法是在main函数之前就全部调用了。所以理论上来说initialize可能永远都不会执行,如果当前类的方法永远不被调用的话。

    下面我们研究一下initializeRuntime中的源码。

    在向对象发送消息时,lookUpImpOrForward函数中会判断当前类是否被初始化,如果没有被初始化,则先进行初始化再调用类的方法。

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
    
    // ....省略好多代码
    
    // 第一次调用当前类的话,执行initialize的代码
    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
    }
    
    // ....省略好多代码
    

    在进行初始化的时候,和load方法的调用顺序一样,会按照继承者链先初始化父类。_class_initialize函数中关键的两行代码是callInitializelockAndFinishInitializing的调用。

    // 第一次调用类的方法,初始化类对象
    void _class_initialize(Class cls)
    {
        Class supercls;
        bool reallyInitialize = NO;
    
        // 递归初始化父类。initizlize不用显式的调用super,因为runtime已经在内部调用了
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
        
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) {
                cls->setInitializing();
                reallyInitialize = YES;
            }
        }
        
        if (reallyInitialize) {
            _setThisThreadIsInitializingClass(cls);
    
            if (MultithreadedForkChild) {
                performForkChildInitialize(cls, supercls);
                return;
            }
            @try {
                // 通过objc_msgSend()函数调用initialize方法
                callInitialize(cls);
            }
            @catch (...) {
                @throw;
            }
            @finally {
                // 执行initialize方法后,进行系统的initialize过程
                lockAndFinishInitializing(cls, supercls);
            }
            return;
        }
        
        else if (cls->isInitializing()) {
            if (_thisThreadIsInitializingClass(cls)) {
                return;
            } else if (!MultithreadedForkChild) {
                waitForInitializeToComplete(cls);
                return;
            } else {
                _setThisThreadIsInitializingClass(cls);
                performForkChildInitialize(cls, supercls);
            }
        }
    }
    

    通过objc_msgSend函数调用initialize方法。

    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    lockAndFinishInitializing函数中会完成一些初始化操作,其内部会调用_finishInitializing函数,在函数内部会调用classsetInitialized函数,核心工作都由setInitialized函数完成。

    static void lockAndFinishInitializing(Class cls, Class supercls)
    {
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
    }
    

    负责初始化类和元类,函数内部主要是查找当前类和元类中是否定义了某些方法,然后根据查找结果设置类和元类的一些标志位。

    void 
    objc_class::setInitialized()
    {
        Class metacls;
        Class cls;
    
        // 获取类和元类对象
        cls = (Class)this;
        metacls = cls->ISA();
    
        bool inherited;
        bool metaCustomAWZ = NO;
        if (MetaclassNSObjectAWZSwizzled) {
            metaCustomAWZ = YES;
            inherited = NO;
        }
        else if (metacls == classNSObject()->ISA()) {
            // 查找是否实现了alloc和allocWithZone方法
            auto& methods = metacls->data()->methods;
            for (auto mlists = methods.beginCategoryMethodLists(), 
                      end = methods.endCategoryMethodLists(metacls); 
                 mlists != end;
                 ++mlists)
            {
                if (methodListImplementsAWZ(*mlists)) {
                    metaCustomAWZ = YES;
                    inherited = NO;
                    break;
                }
            }
        }
        else if (metacls->superclass->hasCustomAWZ()) {
            metaCustomAWZ = YES;
            inherited = YES;
        } 
        else {
            auto& methods = metacls->data()->methods;
            for (auto mlists = methods.beginLists(),
                      end = methods.endLists(); 
                 mlists != end;
                 ++mlists)
            {
                if (methodListImplementsAWZ(*mlists)) {
                    metaCustomAWZ = YES;
                    inherited = NO;
                    break;
                }
            }
        }
        if (!metaCustomAWZ) metacls->setHasDefaultAWZ();
        if (PrintCustomAWZ  &&  metaCustomAWZ) metacls->printCustomAWZ(inherited);
    
        bool clsCustomRR = NO;
        if (ClassNSObjectRRSwizzled) {
            clsCustomRR = YES;
            inherited = NO;
        }
        // 查找元类是否实现MRC方法,如果是则进入if语句中
        if (cls == classNSObject()) {
            auto& methods = cls->data()->methods;
            for (auto mlists = methods.beginCategoryMethodLists(), 
                      end = methods.endCategoryMethodLists(cls); 
                 mlists != end;
                 ++mlists)
            {
                if (methodListImplementsRR(*mlists)) {
                    clsCustomRR = YES;
                    inherited = NO;
                    break;
                }
            }
        }
        else if (!cls->superclass) {
            clsCustomRR = YES;
            inherited = NO;
        } 
        else if (cls->superclass->hasCustomRR()) {
            clsCustomRR = YES;
            inherited = YES;
        } 
        else {
            // 查找类是否实现MRC方法,如果是则进入if语句中
            auto& methods = cls->data()->methods;
            for (auto mlists = methods.beginLists(), 
                      end = methods.endLists(); 
                 mlists != end;
                 ++mlists)
            {
                if (methodListImplementsRR(*mlists)) {
                    clsCustomRR = YES;
                    inherited = NO;
                    break;
                }
            }
        }
        if (!clsCustomRR) cls->setHasDefaultRR();
        if (PrintCustomRR  &&  clsCustomRR) cls->printCustomRR(inherited);
        metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
    }
    

    需要注意的是,initialize方法和load方法不太一样,Category中定义的initialize方法会覆盖原方法而不是像load方法一样都可以调用。


    简书由于排版的问题,阅读体验并不好,布局、图片显示、代码等很多问题。所以建议到我Github上,下载Runtime PDF合集。把所有Runtime文章总计九篇,都写在这个PDF中,而且左侧有目录,方便阅读。

    Runtime PDF

    下载地址:Runtime PDF
    麻烦各位大佬点个赞,谢谢!😁

    相关文章

      网友评论

      • 小包包包:gdb_objc_realized_classes表 以及 namedSelectors表 还有 protocol_map表 是用来干嘛的?
        刘小壮:本质上selector就是字符串
        刘小壮:所有的selector都存储在namedSelectors哈希表中
        刘小壮:gdb_objc_realized_classes是用来存储类和元类的,实际上所有的类和原类对象都存在这个哈希表中

      本文标题:Runtime加载过程

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