美文网首页技术重塑GitHub 中文社区
runtime源码解析--方法加载(runtime初始化)

runtime源码解析--方法加载(runtime初始化)

作者: Jack_deng | 来源:发表于2017-07-17 20:07 被阅读0次

    相比于Runloop,Runtime的源码可就多的多了,所以此文不会把所有的源码都贴上来,只取部分主要的代码。
    这个版本的代码在最新的Mac OS 10.12.2 + Xcode 8.2.1环境下可以正常运行,可以打断点和添加打印代码,方便学习研究。而苹果官方的源代码没法编译。
    阅读runtime源码
    前一定要先了解一些基本概念:Objective-C 中的类和对象

    iOS开发中,main函数是我们熟知的程序启动入口,但实际上并非真正意义上的入口,因为在我们运行程序,再到main方法被调用之间,程序已经做了许许多多的事情,比如我们熟知的runtime的初始化就发生在main函数调用前,还有程序动态库的加载链接也发生在这阶段。本文就从runtime的初始化说起,至于更早的dyld加载确切的说并不属于runtime的范畴,所以就不说了,想了解的可以点击 dyld加载�。

    方法加载的过程

    一切都要从runtime初始化开始,_objc_init方法是runtime初始化的方法

    // jack.deng  _objc_init(void)
    // 这是方法加载的入口
    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();
    
    // map_images 主要是在image加载进内容后对其二进制内容进行解析,初始化里面的类的结构等。
    // load_images 主要是调用call_load_methods。按照继承层次依次调用Class的+load方法然后再是Category的+load方法。
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    一. map_images

    先看map_images的调用栈

    _objc_init(void) -> map_images -> map_images_nolock -> _read_images -> realizeClass

    上述方法我就不贴全部代码了,贴一下关键代码,主要说说它们起了什么作用。

    // jack.deng   ap_images_nolock(unsigned mhCount, co
    void 
    map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                      const struct mach_header * const mhdrs[])
    {
         header_info *hList[mhCount];
         if (firstTime) {
            preopt_init();   // 优化共享缓存的初始化
          }
    
         hList[hCount++] = hi;  // 加载所有的类
    
        if (firstTime) {
            sel_init(selrefCount);  //初始化方法列表并注册内部使用的方法
            arr_init();  // 进行自动释放池和散列表的初始化
         }
        if (hCount > 0) {
            _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
          }
    }
    
    1. map_images在加锁后,将任务交给map_images_nolock
    2. map_images_nolock 首先进行优化共享缓存(optimized shared cache)的初始化,然后加载所有的类,再调用sel_init,初始化方法列表并注册内部使用的方法,arr_init进行自动释放池和散列表的初始化。
    3. _read_images会依次读取镜像文件中相关的类,遵守协议和类别信息并最终实现所有类。
    static Class realizeClass(Class cls)
    // jack.deng  static Class realizeClass(Class cls)
    static Class realizeClass(Class cls)
    {
        runtimeLock.assertWriting();
        const class_ro_t *ro;
        class_rw_t *rw;
        Class supercls;
        Class metacls;
        bool isMeta;
    
        if (!cls) return nil;
        if (cls->isRealized()) return cls;
        assert(cls == remapClass(cls));
    
       // 分配可读写空间,更新class的内存结构
        ro = (const class_ro_t *)cls->data();
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro;
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
            rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
            rw->ro = ro;
            rw->flags = RW_REALIZED|RW_REALIZING;
            cls->setData(rw);
        }
    
        isMeta = ro->flags & RO_META;
        rw->version = isMeta ? 7 : 0;  // old runtime went up to 6
        cls->chooseClassArrayIndex();
    
       // 初始化父类和元类
        supercls = realizeClass(remapClass(cls->superclass));
        metacls = realizeClass(remapClass(cls->ISA()));
    
        // 更新cls与父类和元类的映射关系
        cls->superclass = supercls;
        cls->initClassIsa(metacls);
    
        // Reconcile instance variable offsets / layout.
        // This may reallocate class_ro_t, updating our ro variable.
        if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
        // 设置内存大小
        cls->setInstanceSize(ro->instanceSize);
    
        //更新rw数据
        if (ro->flags & RO_HAS_CXX_STRUCTORS) {
            cls->setHasCxxDtor();
            if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
                cls->setHasCxxCtor();
            }
        }
    
        //将当前类加入其父类的子类列表
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // 处理类数据   Attach categories
        methodizeClass(cls);
        return cls;  
    }
    
    static void methodizeClass(Class cls)
    // jack.deng   static void methodizeClass(Class cls)
    // 首先是将编译阶段便存储在ro中的方法,属性和协议存储在rw的结构里.如果是根类,则主动增加初始方法。
    static void methodizeClass(Class cls)
    {
        runtimeLock.assertWriting();
    
        bool isMeta = cls->isMetaClass();
        auto rw = cls->data();
        auto ro = rw->ro;
    
        //1. 设置类的方法列表,属性列表和遵守协议列表
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
            rw->methods.attachLists(&list, 1);
        }
    
        property_list_t *proplist = ro->baseProperties;
        if (proplist) {
            rw->properties.attachLists(&proplist, 1);
        }
    
        protocol_list_t *protolist = ro->baseProtocols;
        if (protolist) {
            rw->protocols.attachLists(&protolist, 1);
        }
    
        //根类增加初始方法
        if (cls->isRootMetaclass()) {
            // root metaclass
            addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
        }
    
        //2. 处理类别
        category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
        attachCategories(cls, cats, false /*don't flush caches*/);
        if (cats) free(cats);
    }
    

    首先是将编译阶段便存储在ro中的方法,属性和协议存储在rw的结构里.如果是根类,则主动增加初始方法。

    _objc_init结构

    _objc_init结构

    二. load_images

    // jack.deng  load_images(const char *path __unused, const
    void load_images(const char *path __unused, const struct mach_header *mh)
    {
        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_images最重要的事只有一件 -- 调用+load方法.
    也因此如果通过hasLoadMethods发现并没有+load方法,就会转身而走...
    如果发现猎物+load,则会通过prepare_load_methods进行预处理,然后才call_load_methods.

    2.1 prepare_load_methods
    //  jack.deng  void prepare_load_methods(const headerType *mhdr)
    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
        runtimeLock.assertWriting();
    
       //1.class 
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            schedule_class_load(remapClass(classlist[i]));
        }
    
       //2. category
        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);
        }
    }
    
    1. class部分
    // jack.deng   void schedule_class_load(Class cls)
    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        assert(cls->isRealized());  
        if (cls->data()->flags & RW_LOADED) return;
        schedule_class_load(cls->superclass);  // 以递归的方式保证优先处理父类的方法.
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    
    // jack.deng  void add_class_to_loadable_list(Class cls)
    void add_class_to_loadable_list(Class cls)
    {
        IMP method;
    
        loadMethodLock.assertLocked();
        //获取load方法
        method = cls->getLoadMethod();
        if (!method) return;  
    
        //扩容
        if (loadable_classes_used == loadable_classes_allocated) {
            loadable_classes_allocated = loadable_classes_allocated*2 + 16;
            loadable_classes = (struct loadable_class *)
                realloc(loadable_classes,
                                  loadable_classes_allocated *
                                  sizeof(struct loadable_class));
        }
        //存储
        loadable_classes[loadable_classes_used].cls = cls;
        loadable_classes[loadable_classes_used].method = method;
        loadable_classes_used++;
    }
    

    add_class_to_loadable_list中我们能看到将类与load方法存储进loadable_classes的代码段,那么loadable_classes是什么呢?
    loadable_classes是一个loadable_class类型的列表,存储需要调用load方法的类, 而loadable_class是一个只有cls和方法实现method的结构体.
    其中loadable_classes_allocated 标识已分配的内存空间大小,loadable_classes_used则标识已使用的内存空间大小,当内存空间不够时,会进行扩容操作.
    在这一部分,也就是将需要执行+load方法的类与其对应的方法实现存储到loadable_classes表中.

    1. category部分
      add_category_to_loadable_list方法的实现和类的处理相似,将类别和对应的方法实现存储进loadable_categories列表中.

    小结

    prepare_load_methods完成了两件事:

    • 将类与它对应的load方法实现存储进loadable_classes列表;
    • 将分类与它对应的load方法实现存储进loadable_categories列表.
      并且在存储过程中,父类优于子类,类优于分类.
    2.2 call_load_methods
    //  jack.deng  void call_load_methods(void)
    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
        loadMethodLock.assertLocked();
    
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. 调用类的load方法
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. 调用分类的load方法
            more_categories = call_category_loads();
    
          //3. 如果有未处理的继续执行
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    
    • objc_autoreleasePoolPushobjc_autoreleasePoolPop就是自动释放池的实现。
    • 我们重点看的就是这个do循环了,在循环中,首先调用call_class_loads而后是call_category_loads,这就是为什么load方法的调用,类优于分类了.
    // jack.deng  static void call_class_loads(void)
    static void call_class_loads(void)
    {
        //1.初始化数据
        int i;
        struct loadable_class *classes = loadable_classes;//指向用于保存类信息的内存的首地址
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;//标识已分配的内存空间大小,
        loadable_classes_used = 0;//标识已使用的内存空间大小。
    
        //2.调用load方法
        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; 
    
            (*load_method)(cls, SEL_load);
        }
        //3. 清理数据
        if (classes) free(classes);
    }
    

    在调用类别load方法的call_class_loads中,我们可以看到struct loadable_class *classes = loadable_classes;, 这里的loadable_classes便是我们上文提到的在prepare_load_methods阶段load方法存储的地方.

    然后遍历读列表中的类,并调用load方法.而这里需要注意的就是方法调用的方式了:(*load_method)(cls, SEL_load);.
    也就是说load方法的调用是通过直接使用函数内存地址的方式实现的而不是最常见的消息发送objc_msgSend.
    也因为这个原因,类,分类,子类中的load方法调用除了遵循类>子类>分类的顺序外并无其他相关.具体来说,就是子类不会继承父类的实现,分类不会覆盖类的实现.

    // jack.deng  static bool call_category_loads(void)
    static bool call_category_loads(void)
    {
    ...
     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()) 
           {
                (*load_method)(cls, SEL_load);
                cats[i].cat = nil;
            }
        }
    ...
    }
    

    可以看到在类别的+load方法调用中也是如此.

    小结

    call_load_methods完成的任务: 调用类的load方法,调用分类的load方法;

    最后

    总结runtime的初始化:就是完成了类的数据与行为的描述及内存的分配,并加载了load方法。


    runtime初始化

    相关文章

      网友评论

        本文标题:runtime源码解析--方法加载(runtime初始化)

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