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

相比于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初始化
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 整个运行时初始化时 _objc_init 注册的回调 dyld_register_image_state_chan...
    cmhfx1阅读 611评论 0 1
  • 原文链接: http://draveness.me/load/关注仓库,及时获得更新:iOS-Source-Cod...
    Draveness阅读 3,574评论 4 35
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,159评论 0 7
  • Objective-C语言是一门动态语言,他将很多静态语言在编译和链接时期做的事情放到了运行时来处理。这种动态语言...
    tigger丨阅读 1,370评论 0 8