美文网首页
OC底层原理探索—类的加载(3)

OC底层原理探索—类的加载(3)

作者: 十年开发初学者 | 来源:发表于2021-07-21 14:43 被阅读0次

    上一篇我们探索了类的加载流程等一系列方法以及懒加载类和非懒加载类这节课我们来探索下分类的加载流程

    分类的本质

    首先现在main.m中添加LGPerson的分类

    @interface LGPerson (LG)
    
    @property (nonatomic, copy) NSString *cateA_name;
    @property (nonatomic, assign) int    *cateA_age;
    
    - (void)saySomething;
    
    - (void)cateA_instanceMethod1;
    - (void)cateA_instanceMethod2;
    
    
    @end
    @implementation LGPerson (LGA)
    - (void)saySomething{
        NSLog(@"%s",__func__);
    }
    - (void)cateA_instanceMethod1{
        NSLog(@"%s",__func__);
    }
    - (void)cateA_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    @end
    

    clang -rewrite-objc main.m -o main.cpp得到main.cpp文件:

    struct _category_t {
        const char *name; //名字
        struct _class_t *cls;//主类名
        const struct _method_list_t *instance_methods;//实例方法列表
        const struct _method_list_t *class_methods;//类方法列表
        const struct _protocol_list_t *protocols;//协议
        const struct _prop_list_t *properties;//属性
    };
    
    static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_LGA __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "LGPerson",
        0, // &OBJC_CLASS_$_LGPerson,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LGA,
        0,
        0,
        0,
    };
    
    • 分类被翻译成c++文件后,可以看出_category_t是一个结构体
    • 分类没有元类的概念,所以instance_methods存储实例方法class_methods存储类方法
    • 我们可以找到实力方法和类方法的实现代码,但是没有属性的set和get方法,所以分类不会自动生成方法
    • 在结构体中我们没有找到ivar的列表,所以分类中不能声明 成员变量

    分类的加载

    回到上节课所讲的methodizeClass方法

    **********************************************************************/
    static void methodizeClass(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
        bool isMeta = cls->isMetaClass();
        const char *mangledName = cls->nonlazyMangledName();
        if (strcmp(mangledName, "LGPerson") == 0)
        {
            if (!isMeta) {
                printf("%s -LGPerson....\n",__func__);
            }
        }
        auto rw = cls->data();
        auto ro = rw->ro();
        auto rwe = rw->ext();
        
    
        // Methodizing for the first time
        if (PrintConnecting) {
            _objc_inform("CLASS: methodizing class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
    
        // Install methods and properties that the class implements itself.
        //将属性列表、方法列表、协议列表等贴到rwe中
        // 将ro中的方法列表加入到rwe中
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
            if (rwe) rwe->methods.attachLists(&list, 1);
        }
        //将属性添加到rwe中
        property_list_t *proplist = ro->baseProperties;
        if (rwe && proplist) {
            rwe->properties.attachLists(&proplist, 1);
        }
        //将协议添加到rwe中
        protocol_list_t *protolist = ro->baseProtocols;
        if (rwe && protolist) {
            rwe->protocols.attachLists(&protolist, 1);
        }
    }
    
    • 发现方法列表、属性列表、协议列表的加载都有rwe的条件判断,而rwe通过rw->ext()来赋值的,接下来进入rwe的初始化方法
        class_rw_ext_t *ext() const {
            return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
        }
    
        class_rw_ext_t *extAllocIfNeeded() {
            auto v = get_ro_or_rwe();
            if (fastpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
            } else {
                return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
            }
        }
    
    

    extAllocIfNeeded是rwe的初始化方法,他的作用是来判断,如果rwe存在,则直接返回,不存在,则需要初始化开辟一个空间

    反推

    全局搜索extAllocIfNeeded的调用,发现在attachCategories函数中有调用

    然后全局搜索attachCategories函数,发现主要集中在attachToClass 和 load_categories_nolock两个方法中调用

      void attachToClass(Class cls, Class previously, int flags)
        {
       ......
                if (flags & ATTACH_CLASS_AND_METACLASS) {
                    int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                    attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                    attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
                } else {
                    attachCategories(cls, list.array(), list.count(), flags);
                }
                map.erase(it);
            }
        }
    
    ......
      
    };
    
    static void load_categories_nolock(header_info *hi) {
    ......
                if (cls->isStubClass()) {
    
                    if (cat->instanceMethods ||
                        cat->protocols ||
                        cat->instanceProperties ||
                        cat->classMethods ||
                        cat->protocols ||
                        (hasClassProperties && cat->_classProperties))
                    {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                } else {
                    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());
                        }
                    }
                }
            }
        };
    
    }
    ......
    

    接下来我们反推attachToClass方法,全局搜索该方法,发现该方法仅在methodizeClass中调用

    static void methodizeClass(Class cls, Class previously)
    {
    .......
        //// 加入分类中的方法
        // Attach categories.
        if (previously) {
            if (isMeta) {
                objc::unattachedCategories.attachToClass(cls, previously,
                                                         ATTACH_METACLASS);
            } else {
                objc::unattachedCategories.attachToClass(cls, previously,
                                                         ATTACH_CLASS_AND_METACLASS);
            }
        }
        objc::unattachedCategories.attachToClass(cls, cls,
                                                 isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
    
    ......
    

    反推load_categories_nolock方法,全局搜索,发现在_read_images中调用

    走到这一步我们有两条主线

    • _read_images->load_categories_nolock->attachCategories
    • realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories

    分类与主类加载的4种情况

    首先我们现在_read_images、realizeClassWithoutSwift 、load_categories_nolock、methodizeClass、attachToClass、attachCategories
    这6个方法中分别写上判断并打上断点

        const char *mangledName = cls->nonlazyMangledName();
        if (strcmp(mangledName, "LGPerson") == 0)
        {
            if (!isMeta) {
                printf("%s LGPerson....\n",__func__);
            }
        }
    

    接下来我们来实现一个分类

    image.png
    main中打上断点
    image.png

    分类+主类同时实现 load方法

    主类 分类同时实现load

    根据控制台和调用栈的打印 我们得出的流程

    • _read_imags->realizeClassWithoutSwift ->methodizeClass -> attachToClass 函数走完后
    • load_images -> loadAllCategories-> load_categories_nolock -> attachCategories

    分类实现load 主类不实现

    image.png

    在这种情况下attachCategories没有被调用,只是_read_images相关函数的调用。

    流程:_read_imags->realizeClassWithoutSwift ->methodizeClass -> attachToClass

    分类不实现load 主类实现load

    image.png

    这种情况的结果,和上一个一样,attachCategories同样没被调用
    流程:_read_imags->realizeClassWithoutSwift ->methodizeClass -> attachToClass

    主类 分类 都不实现

    image.png
    由此可知在程序启动的时候,类并没有加载,当初始化类的时候,通过方法的慢速查找,一步步的走完流程
    流程 :lookUpImpOrForward->realizeClassWithoutSwift ->methodizeClass -> attachToClass

    流程加载跟踪

    load_categories_nolock

    从上述流程中得知,只有第一种情况分类主类都实现load的情况下才走了attachCategories,接下来我们来探索load_categories_nolock

    image.png
    由上图我们看到,count为1,代表分类数目,打印cat,也就是我们分类的信息,我们可以看到分类名是LGA,instanceMethod和classMethods是有值的。 image.png ·
    lc是一个结构体类型,里面存储cat和hihi是符号表信息

    接下来断点走到attachCategories方法

    image.png

    attachCategories

    断点进入到attachCategories方法

    image.png
    我们看到cat_count等于1代表1个分类,而mlist中的count等于2代表该分类中有两个方法。
    mlist是分类方法的结构体

    接下来断点继续往下走


    image.png image.png
    • ATTACH_BUFSIZ等于64
    • mcount初始化为0这里进行了++操作
    • mlists[ATTACH_BUFSIZ - ++mcount] = mlist这里是吧mlist放到mlists的第63个下标,也就是倒序放入分类,另外从这里得知,一个主类的最多有64个分类

    断点继续往下走,走到prepareMethodLists进行方法排序

    image.png
    最终走到attachLists方法
    image.png

    attachLists

        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;
                array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
                newArray->count = newCount;
                array()->count = newCount;
    
                for (int i = oldCount - 1; i >= 0; I--)
                    newArray->lists[i + addedCount] = array()->lists[I];
                for (unsigned i = 0; i < addedCount; I++)
                    newArray->lists[i] = addedLists[I];
                free(array());
                setArray(newArray);
                validate();
            }
            else if (!list  &&  addedCount == 1) {
                // 0 lists -> 1 list
                list = addedLists[0];
                validate();
            } 
            else {
                // 1 list -> many lists
                Ptr<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;
                for (unsigned i = 0; i < addedCount; I++)
                    array()->lists[i] = addedLists[I];
                validate();
            }
        }
    
    image.png
    • 由打印可知,oldList中存放着主类的方法,count==1是主类的方法个数
    • oldList中存放的是method_list_t *类型的
    • 由打印可知,addedLists存放的也是method_list_t *类型
      -newCount等于2,有代码可知newArray,先将oldList放入到最后的位置,再将addedLists 的数据遍历添加至主类前面,所以说这也是我们常说的主类与分类方法名相同时,分类覆盖主类,但是这里并不是真正的覆盖,只是将分类方法 放到前面

    四种情况的分类数据加载

    分类 主类均有load

    由上面分析可知,分类 主类均有load情况,是在load_imags后全部的动态加载,这里不做更多讲解

    分类实现load 主类不实现

    realizeClassWithoutSwift打上断点

    image.png
    • 这个时候我们发现在ro->baseMethods()中,里面有3个方法
      结论: 由此我们得出分类实现load 主类不实现情况下,分类的加载在编译期就已经处理好了,说明是通过data()加载的
    分类不实现load 主类实现

    这种情况和上述情况一样,分类也是通过data()加载的

    主类 分类均不实现

    通过方法的慢速查找流程 lookUpImpOrForward->realizeClassWithoutSwift ,最终 分类的数据也是通过data()加载的

    多个分类有load 主类有

    load_categories_nolock添加断点

    image.png
    发现count==3由此得出其 流程和分类主类均实现的加载流程是相同的

    相关文章

      网友评论

          本文标题:OC底层原理探索—类的加载(3)

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