美文网首页iOS底层原理
iOS底层原理 - Category实现原理(二)

iOS底层原理 - Category实现原理(二)

作者: julieQY7 | 来源:发表于2020-04-26 19:10 被阅读0次

    通过探索Category底层原理回答以下问题

    1. Category是否可以添加方法、属性、成员变量?Category是否可以遵守Protocol?
    2. Category的本质是什么,在底层是怎么存储的?
    3. Category的实现原理是什么,Catagory中的方法是如何调用到的?
    4. Category中是否有Load方法,load方法是什么时候调用的?
    5. load、initialize的区别

    Category实现原理(一)中我们通过窥探Category底层结构回答了问题1、2,下面我们继续探究。

    Category中的方法调用顺序 - 表象

    创建父类MGCPerson,为其添加分类MGCPerson+SportMGCPerson+Eat,创建子类MGCStudent,并添加分配MGCStudent+Study,然后分别添加方法- (void)life+ (void)life

    @implementation MGCPerson
    - (void)life {
        NSLog(@"MGCPerson : - (void)life");
    }
    
    + (void)life {
        NSLog(@"MGCPerson : + (void)life");
    }
    @end
    
    @implementation MGCPerson (Sport)
    - (void)life {
        NSLog(@"MGCPerson (Sport) : - (void)life");
    }
    
    + (void)life {
        NSLog(@"MGCPerson (Sport) : + (void)life");
    }
    @end
    
    @implementation MGCPerson (Eat)
    - (void)life {
        NSLog(@"MGCPerson (Eat) : - (void)life");
    }
    
    + (void)life {
        NSLog(@"MGCPerson (Eat) : + (void)life");
    }
    @end
    
    @implementation MGCStudent
    - (void)life {
        NSLog(@"MGCStudent : - (void)life");
    }
    
    + (void)life {
        NSLog(@"MGCStudent : + (void)life");
    }
    @end
    
    @implementation MGCStudent (Study)
    - (void)life {
        NSLog(@"MGCStudent (Study) : - (void)life");
    }
    
    + (void)life {
        NSLog(@"MGCStudent (Study) : + (void)life");
    }
    @end
    

    在main函数中创建MGCStudent类,调用life方法,观察打印

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MGCStudent *student = [[MGCStudent alloc] init];
            [student life];
            [MGCStudent life];
            MGCPerson *person = [[MGCPerson alloc] init];
            [person life];
            [MGCPerson life];
        }
        return 0;
    }
    
    // 打印结果
    MGCStudent (Study) : - (void)life
    MGCStudent (Study) : + (void)life
    MGCPerson (Sport) : - (void)life
    MGCPerson (Sport) : + (void)life
    

    观察打印结果我们可以得出一个结论:Category中的方法会”覆盖“原类中的方法;仔细观察我们发现MGCPerson相关的调用打印全部来自MGCPerson (Sport)中的方法,但我们明明在MGCPerson的两个分类MGCPerson (Sport)MGCPerson (Eat)中都实现了life方法,为什么优先调用了MGCPerson (Sport)中的life方法?我们猜测这和文件的编译顺序有关,通过在Build Phase -> Compile source中调整文件顺序观察打印结果,我们发现后编译的分类文件优先调用
    总结起来就是:

    • 分类中的方法实现会"覆盖"原类中的方法
    • 后编译的分类文件优先级更高

    继续研究为什么调用顺序是这样

    Category方法调用顺序 - 本质

    为什么去runtime中找对应源码?

    • OC中的方法调用简单的说就是通过实例对象(或类对象)的isa指针和类对象(或元类对象)的superClass指针去类(或元类)对象中查找方法。
    • 通上篇文章的探究我们知道,Category中的方法、属性等编译后是存储在category_t结构体中的,也就是说编译后分类中的方法并没有合并到类(或元类)中,我们是无法在类对象(或元类对象)中找到Category中的方法的。
    • 但是最终调用的时候我们却可以通过isa和superClass指针找到这些方法。所以我们有理由猜测runtime帮我们做了方法合并

    为什么从void _objc_init(void)开始查找?
    简单的说下APP安装到运行的过程:

    1. 从appStore下载app,签名认证通过后装载到手机磁盘中
    2. 点击APP,系统内核部署好APP(进程)运行的初始环境,找到对应APP的可执行文件(Mach-O文件)
      • Mach-O是苹果系统的一种文件格式,app的可执行文件、动态库、静态库最终都是这种文件格式
    3. 根据Mach-O文件找到dyld的路径,内核加载dyld,dyld从系统留下的原始调用栈引导和启动自己
      • dyld(dynamic link editor) 动态链接器,用于链接动态库、资源、app可执行文件格式的Mach-O文件
      • dyld 本身也是一种Mach-O文件
    4. dyld将APP可执行文件(Mach-O文件)加载到内存中,跟据APP可执行文件中的 LoadCommands中的信息去系统共享缓存区(dyld shared cache)将引用的动态库(Mach-O文件)递归加载进内存
    5. 链接包括可执行文件在内APP所需的所有的Mach-O文件
    6. 初始化除可执行文件外的所有Mach-O文件
    7. 初始化APP可执行文件,调用app中的main函数,到这一步就进入我们熟悉的APP入口main函数了

    _objc_init就是runtime的初始化函数,也就是在上述第6步时调用的。
    在runtime源码中搜索_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();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    可以看到在一系列的初始化后,调用了_dyld_objc_notify_register并且传递了三个函数,去dyld源码中搜索函数_dyld_objc_notify_register的定义

    typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
    typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
    typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);
    // Note: only for use by objc runtime
    // Register handlers to be called when objc images are mapped, unmapped, and initialized.
    // Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
    // Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
    // call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
    // dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
    // dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
    // initializers in that image.  This is when objc calls any +load methods in that image.
    void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                    _dyld_objc_notify_init      init,
                                    _dyld_objc_notify_unmapped  unmapped);
    

    根据注释在_dyld_objc_notify_register中会调用传入的mapped函数,并传入已映射进内存的objc image(objc对应的模块)

    objc image中存储着我们定义的类及分类信息,接下来去runtime源码中查看map_images函数

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

    这个函数实际返回的是map_images_nolock函数的地址,继续查看map_images_nolock函数,map_images_nolock函数中查找了所有包含Objective-Cimage ,然后调用_read_images处理这些image,继续查看_read_images函数
    _read_images函数中找到处理分类的代码块

    #define EACH_HEADER \
        hIndex = 0;         \
        hIndex < hCount && (hi = hList[hIndex]); \
        hIndex++
        // Discover categories. 
        for (EACH_HEADER) { // 遍历image
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count); // 获取当前image中的分类列表
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
            for (i = 0; i < count; i++) { // 遍历分类
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
    
                if (!cls) { // 分类对应的原类已经释放了,清空这个分类,打印信息信息,继续下次循环
                    // Category's target class is missing (probably weak-linked).
                    // Disavow any knowledge of this category.
                    catlist[i] = nil;
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class", 
                                     cat->name, cat);
                    }
                    continue;
                }
    
                // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties)  // 分类有方法、协议或属性
                {
                    addUnattachedCategoryForClass(cat, cls, hi); // 记录这个分类到一个映射表中
                    if (cls->isRealized()) { // 这个类的所有分类已找到
                        remethodizeClass(cls); // 重新整合类中信息
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : "");
                    }
                }
    
                if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        remethodizeClass(cls->ISA());
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category +%s(%s)", 
                                     cls->nameForLogging(), cat->name);
                    }
                }
            }
        }
    

    _read_images函数读取完image中的Category信息后存放起来,然后调用remethodizeClass函数。
    remethodizeClass中又调用了附加分类的方法attachCategories,重点来了,下边我们看下attachCategories函数。注释也很重要,注意下述代码中的注释

    static void  attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));  //malloc一个二维数组存放所有分类的方法列表
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists)); //malloc一个二维数组存放所有分类的属性列表
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists)); //malloc一个二维数组存放所有分类的协议列表
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;
        bool fromBundle = NO;
        while (i--) {  // 注意这里,是i--也就是说是倒序处理分类的
            auto& entry = cats->list[i];
    
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                mlists[mcount++] = mlist; // 按编译倒序(i--)将各分类的方法列表(mlist) 按正序(mcount++) 放进 二维方法列表(mlists)中
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                proplists[propcount++] = proplist;// 按编译倒序(i--)将各分类的属性列表(proplist) 按正序(propcount++) 放进 二维属性列表(proplists)中
            }
    
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;// 按编译倒序(i--)将各分类的协议列表(protolist) 按正序(protocount++) 放进 二维协议列表(protolists)中
            }
        }
    
        auto rw = cls->data();
    
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        rw->methods.attachLists(mlists, mcount); // 将某个类的所有分类的方法列表附加到类的方法列表中
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
        rw->properties.attachLists(proplists, propcount);// 将某个类的所有分类的属性列表附加到类的方法列表中
        free(proplists);
    
        rw->protocols.attachLists(protolists, protocount);// 将某个类的所有分类的协议列表附加到类的方法列表中
        free(protolists);
    }
    

    attachCategories按编译倒序将各分类中的方法、协议、属性列表分别整合成一个二维数组后,又各自调用了attachLists方法将整合后的分类信息附加到原类(target class)的cls->data->rw中

        void attachLists(List* const * addedLists, uint32_t addedCount) {
            if (addedCount == 0) return;
    
            if (hasArray()) {
                // many lists -> many lists
                uint32_t oldCount = array()->count;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 数组扩容
                array()->count = newCount;
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0])); // 从array()->lists位置开始移动oldCount * sizeof(array()->lists[0])字节到array()->lists + addedCount位置
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0])); // addedLists位置来时,拷贝addedCount * sizeof(array()->lists[0])字节到array()->lists,即将拷贝到array()的前addedCount位置上
            }
            else if (!list  &&  addedCount == 1) {
                // 0 lists -> 1 list
                list = addedLists[0];
            } 
            else {
                // 1 list -> many lists
                List* oldList = list;
                uint32_t oldCount = oldList ? 1 : 0;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)malloc(array_t::byteSize(newCount)));
                array()->count = newCount;
                if (oldList) array()->lists[addedCount] = oldList;
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
        }
    

    分析上述代码,可以看到是先将原类中的方法后移,然后再将分类中的方法加入到原类中的,到此我们可以得出以下结论

    • 分类中的方法优先级高于原类中的方法
    • 后编译的分类优先级高于先编译的分类
    • 我们常说的分类方法覆盖原类方法并不是真正的覆盖,只是objc_msgSend在分类中找到方法实现后不再继续查找

    Category中的+load方法

    同样的我们先分别在类及其分类中实现+load+ (void)initialize方法,供后续分析使用

    @interface MGCPerson : NSObject
    @end
    @implementation MGCPerson
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    
    @implementation MGCPerson (Sport)
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    
    @implementation MGCPerson (Eat)
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    
    @interface MGCStudent : MGCPerson
    @end
    @implementation MGCStudent
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    
    @implementation MGCStudent (Study)
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    
    @implementation MGCStudent (Holiday)
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    
    @interface MGCDog : NSObject
    @end
    @implementation MGCDog
    + (void)load {
        NSLog(@"%s", __func__);
    }
    
    + (void)initialize {
        NSLog(@"%s", __func__);
    }
    @end
    

    添加好代码后,不做任何调用和对象创建,直接运行程序,打印如下

    +[MGCDog load]
    +[MGCPerson load]
    +[MGCStudent load]
    +[MGCPerson(Eat) load]
    +[MGCPerson(Sport) load]
    +[MGCStudent(Holiday) load]
    +[MGCStudent(Study) load]
    

    可以发现未做任何调用和对象创建的情况下,也会执行+ (void)load方法
    尝试在xcode->targets->build phases->compile sources中调整文件编译顺序,发现

    • 类中的load方法始终优先于分类调用,且不受编译顺序影响
    • 父类中的load方法优先于子类调用,且不受编译顺序影响
    • 无继承关系的类,先编译的先调用
    • 分类中的load方法先编译的先调用,且与其对应原类的继承关系无关

    既然我们没有调用,我们猜测是runtime启动时调用了load方法,继续查看runtime的入口函数_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();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    从前边对void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped);的分析我们知道第二个参数load_images函数中调用了+load方法,从这里也可以看出load_images中的load不是加载的意思,是load方法的意思

    void load_images(const char *path __unused, const struct mach_header *mh)
    {
        // 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
        {
            rwlock_writer_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh); // 准备工作
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        call_load_methods(); // 调用load方法
    }
    

    load_images中先调用了prepare_load_methods再进一步调用了void call_load_methods(void)
    先看下prepare_load_methods做了什么处理,注释很重要,不要忽略它

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertWriting();
    
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count); // 获取所有类
        for (i = 0; i < count; i++) { // 正序遍历所有类(编译顺序)
            schedule_class_load(remapClass(classlist[i])); // 整理类的load方法
        }
    
        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;  // category for ignored weak-linked class
            realizeClass(cls);
            assert(cls->ISA()->isRealized());
            add_category_to_loadable_list(cat); // 正序添加到表中
        }
    }
    

    分析上述代码可知:类中的load方法优先于Category中的load方法被规划入表中;且类及分类各自按编译正序规划load方法到表中。
    继续查看schedule_class_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; // 如果已经处理过此类的load方法,则不再处理,所以每个load方法只会调用一次
    
        // Ensure superclass-first ordering
        schedule_class_load(cls->superclass); // 递归调用schedule_class_load方法,并传入父类
    
        add_class_to_loadable_list(cls); // 将类中的load方法加入表中
        cls->setInfo(RW_LOADED);  // 置为已处理
    }
    

    分析上述代码可知:父类中的load方法优先于子类中的load方法加入表中。
    到此准备工作就做完了,接下来看下调用void call_load_methods(void)

    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(); // 调用所有类的load方法,地都用完成后loadable_classes_used置为0
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads(); // 调用所有分类的load方法
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    分析上述代码可知:调用load方法时优先调用类中的load方法,然后再调用分类中的load方法

    static void call_class_loads(void)
    {
        int i;
        
        // Detach current loadable list.
        struct loadable_class *classes = loadable_classes;  // 从表中取出所有类
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        // Call all +loads for the detached list.
        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; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            (*load_method)(cls, SEL_load);// 调用load方法
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    

    分析上述代码可知:类中的load方法按表中正序调用,结合前边load方法的入表顺序可知,先编译的类先调用,且父类优先于子类调用。
    查看call_category_loads(void)方法

        struct loadable_category *cats = loadable_categories;
        int used = loadable_categories_used;
        int allocated = loadable_categories_allocated;
        loadable_categories = nil;
        loadable_categories_allocated = 0;
        loadable_categories_used = 0;
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) { // 按表中正序调用
            Category cat = cats[i].cat;
            load_method_t load_method = (load_method_t)cats[i].method;
            Class cls;
            if (!cat) continue;
    
            cls = _category_getClass(cat);
            if (cls  &&  cls->isLoadable()) {
                if (PrintLoading) {
                    _objc_inform("LOAD: +[%s(%s) load]\n", 
                                 cls->nameForLogging(), 
                                 _category_getName(cat));
                }
                (*load_method)(cls, SEL_load); // 调用load方法
                cats[i].cat = nil;
            }
        }
    

    分析上述代码可知:分类中的load方法按表中正序调用,结合前边入表顺序可知,先编译的分类先调用。
    综合以上分析,已经验证了本小结开始时的现象分析结论,这里不再赘述

    Category中的+ (void)initialize方法

    同样先看现象,代码和上一小结(Category中的+load方法)中一样,这里不再列出
    上一小结中我们在不添加任何方法调用、对象创建的情况下直接运行程序,发现调用了+load方法,但是并没有调用+ (void)initialize
    尝试添加如下代码

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [MGCStudent class];
            [[MGCDog alloc] init];
            [[MGCDog alloc] init];
            [[MGCDog alloc] init];
            [[MGCDog alloc] init];
        }
        return 0;
    }
    

    打印结果

    +[MGCPerson(Eat) initialize]
    +[MGCStudent(Study) initialize]
    +[MGCDog initialize]
    

    可以看到在我们没有调用+ (void)initialize的情况下,+ (void)initialize方法依然被调用了,且无论调用的是类类方法还是实例方法,+ (void)initialize都会被调用并且只会调用一次。
    因此我们猜测是类第一次接收到消息时调用了+ (void)initialize方法。即第一次接收objc_msgSend消息时调用了+ (void)initialize方法。
    尝试去runtime中查找objc_msgSend方法,这个方法的源码是汇编的。
    最终调用到了void _class_initialize(Class cls)方法

    void _class_initialize(Class cls) {
        Class supercls;
        bool reallyInitialize = NO;
    
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls); // 递归调用_class_initialize方法,并传入父类指针
        }
        
        // Try to atomically set CLS_INITIALIZING.
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) { // 如果类没有调用Initialize方法,则准备调用
                cls->setInitializing();
                reallyInitialize = YES;
            }
        }
    
        if (reallyInitialize) {
              callInitialize(cls); // 调用当前类的callInitialize方法
        }
    }
    

    分析上述代码可知:在调用自己的callInitialize之前会先调用父类的callInitialize

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

    分析上述代码可知:Initialize最终是通过objc_msgSend调用的,既然最终是通过objc_msgSend调用的,那就遵从objc_msgSend方法调用的原理。即分类方法会"覆盖"原类的中的方法。
    综合以上可知:

    • 父类中的Initialize优先于子类中的Initialize方法调用
    • 分类中的Initialize优先于类中的Initialize方法调用
      注意:如果子类及子类分类中都没有实现Initialize方法,根据objc_msgSend方法查找顺序,最终会调用父类的Initialize方法。也就是说上一小节中定义的MGCStudent对象及其分类MGCStudent (Study)MGCStudent (Holiday)如果都没实现Initialize,调用[MGCStudent class]会调用两次Initialize方法,一次是递归调用时父类(MGCPerson)调用的,一次是自己调用时objc_msgSend根据方法查找顺序自己(MGCStudent)调用的

    相关文章

      网友评论

        本文标题:iOS底层原理 - Category实现原理(二)

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