美文网首页
iOS-OC底层12:类的加载

iOS-OC底层12:类的加载

作者: MonKey_Money | 来源:发表于2020-10-16 23:50 被阅读0次

    前沿

    我们已经了解了dyly和objc关联,接下来我们重点研究一下read_images下和我们息息相关的类的处理

    read_images下的if (!doneOnce)当第一次进入这个方法时

    1.DisableNonpointerIsa = true;当swift代码过旧,app过旧还有app包含__DATA,__objc_rawisa部分时
    2.gdb_objc_realized_classes:初始化实现class的表
    下面我们着重关注对class的处理
    我们先做准备类

    @interface MyPerson : NSObject
    
    @property (nonatomic, copy) NSString *my_name;
    @property (nonatomic, assign) int my_age;
    
    -(void)myInstanceMethod2;
    
    -(void)myInstanceMethod1;
    
    -(void)myInstanceMethod3;
    
    +(void)myClassMethod;
    
    @end
    @implementation MyPerson
    +(void)load {
    }
    -(void)myInstanceMethod2 {
        NSLog(@"%s",__func__);
    }
    
    -(void)myInstanceMethod1{
        NSLog(@"%s",__func__);
    }
    
    -(void)myInstanceMethod3{
        NSLog(@"%s",__func__);
    }
    
    +(void)myClassMethod{
        NSLog(@"%s",__func__);
    }
    @end
    

    readClass

    我们在readClass做一些出来,来帮助我们针对性的研究我们自定义的类,方便我们打断点进行调试

        const char *mangledName  = cls->mangledName();
        const char *PersonName = "MyPerson";
    
        // printf("诶唷: %s \n ",mangledName);
    
        if (strcmp(mangledName, PersonName) == 0) {
            auto kc_ro = (const class_ro_t *)cls->data();
            printf("readClass: 这个是我要研究的 %s \n",PersonName);
        }
    

    在我们的if判断内打断点,防止系统的类干扰我们研究,我们通过单步调试来到了

          addNamedClass(cls, mangledName, replacing);
            addClassTableEntry(cls);
    

    addNamedClass

    在跳用addNamedClass之前我们p cls的出(Class) $3 = 0x0000000100008268

    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);
            addNonMetaClass(cls);
        } else {
            NXMapInsert(gdb_objc_realized_classes, name, cls);
        }
    }
    

    addNamedClass主要是把类名加到read_images中第一次进入初始化的表中,经过
    addNamedClass之后我们p cls 的结果是(Class) $4 = MyPerson,我们可以得出addNamedClass是让cls的地址和类名关联起来。

    addClassTableEntry

    通过打断可知addClassTableEntry对MyPerson类没做任何处理

    解惑

    我们在read_images方法中的关于non-lazy进行代码处理

                Class cls = remapClass(classlist[i]);
                
                const char *mangledName  = cls->mangledName();
                const char *PersonName = "MyPerson";
               
                if (strcmp(mangledName, PersonName) == 0) {
                    auto kc_ro = (const class_ro_t *)cls->data();
                    printf("_getObjc2NonlazyClassList: 这个是我要研究的 %s \n",PersonName);
                }
    

    通过在if打断点,程序没有走到if判断中。我们貌似对整个read_images中关于类的都研究完毕,但是我们发现这个注释关于load的方法,所以我们大胆尝试对MyPerson增加load方法,奇迹出现if中走到了

    懒加载类与非懒加载类

    官方在对类进行处理的时候, 为了提高对类处理的效率以及性能, 就对类进行了识别, 当类需要使用的时候, 系统才会对类进行实现. 如果没有使用就不会实现. 当需要实现才进行加载的类就被称为懒加载类. 反之无论是否使用到这个类, 都对这个类进行加载的类就被称为非懒加载类.
    懒加载类和非懒加载类的区别就是是否实现load方法,实现load的方法是非懒加载类,没有实现load方法是懒加载类,另一个原因可能是在load_image中有调用load方法,如果类没有被加载完备,调用load方法可能会有问题。赖加载类在lookupImp是有realizeClass


    image.png

    realizeClassWithoutSwift

    在此函数中我们也进行针对性的处理

        const char *mangledName  = cls->mangledName();
        const char *PersonName = "MyPerson";
    
        if (strcmp(mangledName, PersonName) == 0) {
            auto kc_ro = (const class_ro_t *)cls->data();
            auto kc_isMeta = kc_ro->flags & RO_META;
            if (!kc_isMeta) {
                printf("%s: 这个是我要研究的 %s \n",__func__,PersonName);
            }
        }
    

    1.从cls中读ro数据 ,(ro数据是编译的时候写入到镜像文件,从镜像文件中读到的ro)。然后把ro设置到rw中,然后rw设置到cls内
    2.写入此类的索引chooseClassArrayIndex

    1. realizeClassWithoutSwift父类和元类,递归
      4.设置其他的信息如isa,NonpointerIsa等等
      5.设置父类和元类 cls->superclass = supercls; cls->initClassIsa(metacls);
      6.methodizeClass方法化现在的类
      在对rw设值之前和之后打印cls
     x/4gx cls
    0x100008280: 0x0000000100008258 0x000000010034c140
    0x100008290: 0x0000000100346440 0x0000000000000000
     cls->setData(rw);之后
    x/4gx cls
    0x100008280: 0x0000000100008258 0x000000010034c140
    0x100008290: 0x0000000100346440 0x0000000000000000
    

    两次中的bit值为什么都是空的呢 0x0000000000000000,在后面我们再解答这个问题

    methodizeClass

    1.prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
    bool baseMethods, bool methodsFromBundle)中的addedLists为二维数组。fixupMethodList是对方法的排序,fixupMethodList中的排序是按照selector 的地址进行排序的
    2.if (rwe) rwe->methods.attachLists(&list, 1); rwe为null
    rwe值什么情况才有会有,rwe的初始化是cls->data()->extAllocIfNeeded(),全局搜索可知关联分类(attachCategories),类设置版本号(class_setVersion),增加方法(addMethod),增加协议(class_addProtocol),增加属性(_class_addProperty)

    添加category后的流程

    在main文件中添加MyPerson的分类

    @interface MyPerson (LG)
    @property (nonatomic, copy) NSString *cate_name;
    @property (nonatomic, assign) int cate_age;
    - (void)cate_instanceMethod1;
    - (void)cate_instanceMethod3;
    - (void)cate_instanceMethod2;
    + (void)cate_sayClassMethod;
    @end
    @implementation MyPerson (LG)
    + (void)load {
        
    }
    - (void)cate_instanceMethod1{
        NSLog(@"%s",__func__);
    }
    - (void)cate_instanceMethod3{
        NSLog(@"%s",__func__);
    }
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    + (void)cate_sayClassMethod{
        NSLog(@"%s",__func__);
    }
    @end
    

    探索分类的本质,通过clang -rewrite-objc main.m -o main2.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_MyPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "MyPerson", //name 
        0, // &OBJC_CLASS_$_MyPerson, //cls
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyPerson_$_LG, //instance_methods
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyPerson_$_LG, //class_methods
        0, //protocols
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyPerson_$_LG, //properties
    };
    

    分类中添加的属性不会有set和get方法,我们可以通过runtime的关联实现.

    methodizeClass有对分类的处理,objc::unattachedCategories.attachToClass
    attachToClass内有一个方法attachCategories,这个貌似是对分类的处理。但是在attachToClass中没有捕捉到对attachCategories的调用。但是我们可以在attachCategories做针对性分析

      if (strcmp(mangledName, LGPersonName) == 0) {
            bool kc_isMeta = cls->isMetaClass();
            auto kc_rw = cls->data();
            auto kc_ro = kc_rw->ro();
            if (!kc_isMeta) {
                printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
            }
        }
    

    在写入的代码里打断点,看堆栈信息,


    image.png

    attachCategories

    1.对rwe进行初始化auto rwe = cls->data()->extAllocIfNeeded();

    相关文章

      网友评论

          本文标题:iOS-OC底层12:类的加载

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