美文网首页底层原理
category中方法的加载流程

category中方法的加载流程

作者: 晨曦的简书 | 来源:发表于2021-04-25 20:42 被阅读0次

    分类方法的加载顺序

    首先我们定义一个Person类,并给Person添加一个- (void)walk方法并实现这个方法,,然后再给Person添加一个Person+student的分类,并也实现- (void)walk方法,然后实例化一个对象p,让p调用- (void)walk方法,并打印Person类的方法列表。

    Person类方法列表打印.png

    通过打印我们可以看到,执行的是分类的walk方法,而且打印Person方法列表,会发现有两个walk方法,第一个为分类的walk方法。

    这里我们可以通runtime源码来探究一下分类的加载过程。

    • 第一步: runtime 第一个执行的方法,系统在这里执行了一系列的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();
    
    //map_images加载镜像文件
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    
    • 第二步:点到map_images方法里面。 在这里加载所有的镜像文件,在这里只看分类加载相关的方法。
    //1
    void
    map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    
    //2.
    void 
    map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                      const struct mach_header * const mhdrs[])
    
    //3.
    void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
    
    //4.
    static void load_categories_nolock(header_info *hi)
    
    • 第三步:通过一步一步的点到方法里面,我们来到load_categories_nolock方法里面,这里就是分类方法添加到方法列表的过程。
    static void load_categories_nolock(header_info *hi) {
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
        size_t count;
        auto processCatlist = [&](category_t * const *catlist) {
            for (unsigned i = 0; i < count; i++) {
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
                locstamped_category_t lc{cat, hi};
    
                if (!cls) {
                    // Category's target class is missing (probably weak-linked).
                    // Ignore the category.
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class",
                                     cat->name, cat);
                    }
                    continue;
                }
    
                // Process this category.
                if (cls->isStubClass()) {
                    // Stub classes are never realized. Stub classes
                    // don't know their metaclass until they're
                    // initialized, so we have to add categories with
                    // class methods or properties to the stub itself.
                    // methodizeClass() will find them and add them to
                    // the metaclass as appropriate.
                    if (cat->instanceMethods ||
                        cat->protocols ||
                        cat->instanceProperties ||
                        cat->classMethods ||
                        cat->protocols ||
                        (hasClassProperties && cat->_classProperties))
                    {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                } else {
                    // First, register the category with its target class.
                    // Then, rebuild the class's method lists (etc) if
                    // the class is realized.
                    //实例方法相关
                    if (cat->instanceMethods ||  cat->protocols
                        ||  cat->instanceProperties)
                    {
                        if (cls->isRealized()) {
                            attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                        } else {
                            objc::unattachedCategories.addForClass(lc, cls);
                        }
                    }
                    //类方法相关
                    if (cat->classMethods  ||  cat->protocols
                        ||  (hasClassProperties && cat->_classProperties))
                    {
                        if (cls->ISA()->isRealized()) {
                            attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                        } else {
                            objc::unattachedCategories.addForClass(lc, cls->ISA());
                        }
                    }
                }
            }
        };
    
        processCatlist(_getObjc2CategoryList(hi, &count));
        processCatlist(_getObjc2CategoryList2(hi, &count));
    }
    
    • 第4步:找到attachCategories方法并进入
    static void
    attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                     int flags)
    {
        if (slowpath(PrintReplacedMethods)) {
            printReplacements(cls, cats_list, cats_count);
        }
        if (slowpath(PrintConnecting)) {
            _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                         cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                         cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
        }
    
        /*
         * Only a few classes have more than 64 categories during launch.
         * This uses a little stack, and avoids malloc.
         *
         * Categories must be added in the proper order, which is back
         * to front. To do that with the chunking, we iterate cats_list
         * from front to back, build up the local buffers backwards,
         * and call attachLists on the chunks. attachLists prepends the
         * lists, so the final result is in the expected order.
         */
        constexpr uint32_t ATTACH_BUFSIZ = 64;
        method_list_t   *mlists[ATTACH_BUFSIZ];
        property_list_t *proplists[ATTACH_BUFSIZ];
        protocol_list_t *protolists[ATTACH_BUFSIZ];
    
        uint32_t mcount = 0;
        uint32_t propcount = 0;
        uint32_t protocount = 0;
        bool fromBundle = NO;
        bool isMeta = (flags & ATTACH_METACLASS);
        auto rwe = cls->data()->extAllocIfNeeded();
    
        for (uint32_t i = 0; i < cats_count; i++) {
            auto& entry = cats_list[i];
    
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                if (mcount == ATTACH_BUFSIZ) {
                    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                    rwe->methods.attachLists(mlists, mcount);
                    mcount = 0;
                }
                mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist =
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                if (propcount == ATTACH_BUFSIZ) {
                    rwe->properties.attachLists(proplists, propcount);
                    propcount = 0;
                }
                proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
            if (protolist) {
                if (protocount == ATTACH_BUFSIZ) {
                    rwe->protocols.attachLists(protolists, protocount);
                    protocount = 0;
                }
                protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
            }
        }
    
        if (mcount > 0) {
            prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
            rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
            if (flags & ATTACH_EXISTING) flushCaches(cls);
        }
    
        rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    
        rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
    }
    

    在这里会把分类的方法贴到类的方法列表里面。分类中的方法只会添加到相同方法的前面。

    同名分类的方法添加顺序

    多个分类.png
    像这里如果有多个分类Person+studentPerson+Teacher的话,且分类中有同名方法的话,就会根据分类文件的先后顺序,Person+ student分类中的同名方法会添加到相同方法的前面。所以调用同名方法的话,会执行Person+ student分类中的方法。

    load方法的调用流程

    //第一步进的load_images方法
    void load_images(const char *path __unused, const struct mach_header *mh)
    {
        if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
            didInitialAttachCategories = true;
            loadAllCategories();
        }
    
        // Return without taking locks if there are no +load methods here.
        if (!hasLoadMethods((const headerType *)mh)) return;
    
        recursive_mutex_locker_t lock(loadMethodLock);
    
        // Discover load methods
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        //调用load方法,在所有的image映射到内存之后,才调用load方法。
        call_load_methods();
    }
    
    //第二步: 准备加载load方法
    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertLocked();
    
    //遍历加载类中的load方法
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            schedule_class_load(remapClass(classlist[i]));
        }
    
    //遍历加载分类中的load方法
        category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            if (!cls) continue;  // category for ignored weak-linked class
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class extensions and categories on Swift "
                            "classes are not allowed to have +load methods");
            }
            realizeClassWithoutSwift(cls, nil);
            ASSERT(cls->ISA()->isRealized());
            add_category_to_loadable_list(cat);
        }
    }
    
    //第三步:一层一层遍历加载父类的load方法
    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        ASSERT(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
    
        // Ensure superclass-first ordering
        schedule_class_load(cls->superclass);
    
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    
    //第四步:加载完毕,调用所有类及分类的load方法
    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    
    1. +load方法加载顺序:父类> 子类> 分类 (load方法都会加载)
      注意:(如果分类中有A,B,顺序要看A,B加入工程中顺序,先编译先调用) ,
      可能结果:( 父类> 子类> 分类A> 分类B ) 或者( 父类> 子类> 分类B> 分类A )
    2. +load方法不会被覆盖(比如有父类,子类,分类A,分类B,这四个load方法都会加载)。
    3. +load方法调用在main函数前。

    initialize方法

    initialize方法调用时机: 当前self接收到第一条消息的时候会调用,可以考虑把在load方法里面的处理放到这个方法里面。因为load方法再main函数之前执行,会影响app启动速度。

    +initialize方法加载顺序有以下4种情况:
    (1)分类 (子类没有initialize方法,父类存在或者没有initialize方法)
    (2)分类> 子类 (多个分类就看编译顺序,只有存在一个)
    (3)父类> 子类 (分类没有initialize方法)
    (4)父类 (子类,分类都没有initialize方法)
    总结+initialize:

    1. 当调用子类的+ initialize方法时候,先调用父类的,如果父类有分类, 那么父类的分类中的+ initialize会覆盖掉父类的。
    2. 分类的+ initialize会覆盖掉父类的
    3. 子类的+ initialize不会覆盖分类的
    4. 父类的+ initialize不一定会调用, 因为有可能父类的分类重写了它
    5. 发生在main函数后。

    持续更新改正中......

    相关文章

      网友评论

        本文标题:category中方法的加载流程

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