美文网首页面试宝点iOS深入原理iOS
iOS开发-class_ro_t和class_rw_t的区别

iOS开发-class_ro_t和class_rw_t的区别

作者: 断念的决绝 | 来源:发表于2020-12-25 10:18 被阅读0次

    iOS开发-class_ro_t和class_rw_t的区别

    文章目录

    class_ro_t

    class_ro_t存储了当前类在编译期就已经确定的属性方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的class_rw_t中。

    ro即表示read only,是无法进行修改的。

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
    
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    };
    
    

    class_rw_t

    ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t中:

    // 可读可写
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro; // 指向只读的结构体,存放类初始信息
    
        /*
         这三个都是二位数组,是可读可写的,包含了类的初始内容、分类的内容。
         methods中,存储 method_list_t ----> method_t
         二维数组,method_list_t --> method_t
         这三个二位数组中的数据有一部分是从class_ro_t中合并过来的。
         */
        method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放类方法)
        property_array_t properties; // 属性列表
        protocol_array_t protocols; //协议列表
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        //...
        }
    
    

    class_rw_t生成时机

    class_rw_t生成在运行时,在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtimerealizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。

    类的realizeClass运行之前:

    在这里插入图片描述

    类的realizeClass运行之后:

    在这里插入图片描述

    细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_tclass_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容

    摘自 https://www.jianshu.com/p/823eaedb3697

    分类方法加载到class_rw_t的流程

    • 程序启动后,通过编译之后,Runtime 会进行初始化,调用 _objc_init

    _objc_initdyld驱动,这个阶段会注册3个回调,分别是mapped,init,unmapped

    /***********************************************************************
    * _objc_init
    * Bootstrap initialization. Registers our image notifier with dyld.
    * Called by libSystem BEFORE library initialization time
    **********************************************************************/
    
    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(); //tls指的是局部线程存储,可以将数据存储在线程一个公共区域,例如pthread_setspecific(),在autoreleasepool和堆栈信息获取时都有涉及
        static_init(); //执行c++静态构造函数
        lock_init(); //这里获取两个的线程优先级 后台优先级线程以及主线程
        exception_init(); //这里初始化libobjc的exception处理系统
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    
    

    比较核心的是_dyld_objc_notify_register方法

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    

    map_images

    • 然后会 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
    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 方法主要做了下面这些事情:

    _read_images 方法写了很长,其实就是做了一件事,将Mach-O文件的section依次读取,并根据内容初始化runtime的内存结构。

    1. 是否需要禁用isa优化。这里有三种情况:使用了swift 3.0前的swift代码。OSX版本早于10.11。在OSX系统下,Mach-ODATA段明确指明了__objc_rawisa(不使用优化的isa).

    苹果从ARM64位架构开始,对isa进行了优化,将其定义成一个共用体(union)结构,结合 位域 的概念以及 位运算 的方式来存储更多类相关信息。isa指针需要通过与一个叫ISA_MASK的值(掩码)进行二进制&运算,才能得到真实的class/meta-class对象的地址。
    参考文章 https://www.jianshu.com/p/30de582dbeb7

    1. 判断是否禁用了tagged pointer
    2. __objc_classlist section中读取class list
    3. __objc_classrefs section中读取class 引用的信息,并调用remapClassRef方法来处理。
    4. __objc_selrefs section中读取selector的引用信息,并调用sel_registerNameNoLock方法处理。
    5. __objc_protolist section中读取clsProtocol信息,并调用readProtocol方法来读取Protocol信息。
    6. __objc_protorefs section中读取protocol的ref信息,并调用remapProtocolRef方法来处理。
    7. __objc_nlclslist section中读取non-lazy class信息,并调用static Class realizeClass(Class cls)方法来实现这些class。realizeClass方法核心是初始化objc_class数据结构,赋予初始值。
    8. __objc_catlist section中读取category信息,并调用addUnattachedCategoryForClass方法来为类或元类添加对应的方法,属性和协议。
    • 调用 reMethodizeClass:,这个方法是重新方法化的意思。
    • reMethodizeClass:方法内部会调用attachCategories: ,这个方法会传入 ClassCategory,会将方法列表,协议列表等与原有的类合并。最后加入到 class_rw_t 结构体中。

    load_images

    构造好 class_rw_t 之后,load_images 调用 call_load_methods 就是开始调用类的+load方法和分类的+load方法了

    /***********************************************************************
    * call_load_methods
    * Call all pending class and category +load methods.
    * Class +load methods are called superclass-first. 
    * Category +load methods are not called until after the parent class's +load.
    * 
    * This method must be RE-ENTRANT, because a +load could trigger 
    * more image mapping. In addition, the superclass-first ordering 
    * must be preserved in the face of re-entrant calls. Therefore, 
    * only the OUTERMOST call of this function will do anything, and 
    * that call will handle all loadable classes, even those generated 
    * while it was running.
    *
    * The sequence below preserves +load ordering in the face of 
    * image loading during a +load, and make sure that no 
    * +load method is forgotten because it was added during 
    * a +load call.
    * Sequence:
    * 1\. Repeatedly call class +loads until there aren't any more
    * 2\. Call category +loads ONCE.
    * 3\. Run more +loads if:
    *    (a) there are more classes to load, OR
    *    (b) there are some potential category +loads that have 
    *        still never been attempted.
    * Category +loads are only run once to ensure "parent class first" 
    * ordering, even if a category +load triggers a new loadable class 
    * and a new loadable category attached to that class. 
    *
    * Locking: loadMethodLock must be held by the caller 
    *   All other locks must not be held.
    **********************************************************************/
    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;
    }
    
    

    unmap_image

    unmap_image 调用 _unload_image

    涉及一些资源的释放,例如 unattached list+load queue,将每个类分离后,进行释放

    /***********************************************************************
    * _unload_image
    * Only handles MH_BUNDLE for now.
    * Locking: write-lock and loadMethodLock acquired by unmap_image
    **********************************************************************/
    void _unload_image(header_info *hi)
    {
        size_t count, I;
    
        loadMethodLock.assertLocked();
        runtimeLock.assertWriting();
    
        // Unload unattached categories and categories waiting for +load.
    
        category_t **catlist = _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            if (!cat) continue;  // category for ignored weak-linked class
            Class cls = remapClass(cat->cls);
            assert(cls);  // shouldn't have live category for dead class
    
            // fixme for MH_DYLIB cat's class may have been unloaded already
    
            // unattached list
            removeUnattachedCategoryForClass(cat, cls);
    
            // +load queue
            remove_category_from_loadable_list(cat);
        }
    
        // Unload classes.
    
        // Gather classes from both __DATA,__objc_clslist 
        // and __DATA,__objc_nlclslist. arclite's hack puts a class in the latter
        // only, and we need to unload that class if we unload an arclite image.
    
        NXHashTable *classes = NXCreateHashTable(NXPtrPrototype, 0, nil);
        classref_t *classlist;
    
        classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (cls) NXHashInsert(classes, cls);
        }
    
        classlist = _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (cls) NXHashInsert(classes, cls);
        }
    
        // First detach classes from each other. Then free each class.
        // This avoid bugs where this loop unloads a subclass before its superclass
    
        NXHashState hs;
        Class cls;
    
        hs = NXInitHashState(classes);
        while (NXNextHashState(classes, &hs, (void**)&cls)) {
            remove_class_from_loadable_list(cls);
            detach_class(cls->ISA(), YES);
            detach_class(cls, NO);
        }
        hs = NXInitHashState(classes);
        while (NXNextHashState(classes, &hs, (void**)&cls)) {
            free_class(cls->ISA());
            free_class(cls);
        }
    
        NXFreeHashTable(classes);
    
        // XXX FIXME -- Clean up protocols:
        // <rdar://problem/9033191> Support unloading protocols at dylib/image unload time
    
        // fixme DebugUnload
    }
    

    相关文章

      网友评论

        本文标题:iOS开发-class_ro_t和class_rw_t的区别

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