Ruby 重写方法的疑问

yxmmrwx · 2025年01月08日 · 最后由 Mark24 回复于 2025年01月09日 · 91 次阅读

重写方法时,如果没有 alias_method :old_method,:method,那么内存中是否还存在着之前方法的实现?还是说被覆盖了?

內存中還存在,但下一次 gc 之後就未必。ruby 每個類會有一個 hash table 用來存方法,確切地說是 id_table,因為 key 只能是 id(類似 symbol),id_table 的 value 就是方法。當你重新定義方法的時候,ruby 會用 id 在 id_table 裏找,然後直接替換掉舊的。在下一次 gc 的時候,ruby 會標記所有可達的對象,這時候 id_table 裏可達的只有你定義的新方法,舊的如果沒有被標記最後就會被回收。

非常感谢

答案:

  • 没有复制。

alias 做的事情:将方法入口名,改为 新名字,更新方法环境变量(所在类)。新名字,最后引用原始的方法。

CRuby 源码 简化

void
rb_alias(VALUE klass, ID alias_name, ID original_name)
{

    // 1)查找原始方法 
    // .....
    orig_me = search_method(klass, original_name, &defined_class);

    // 2) 继承关键信息
    // .....
    // 如果原始方法是 super 方法(VM_METHOD_TYPE_ZSUPER),则将 klass 设置为其父类,并将 original_name 设置为原始方法的原始 ID,然后跳转到 again 标签重新查找原始方法。
    // 如果原始方法是别名方法(VM_METHOD_TYPE_ALIAS),则获取原始方法的可见性,并将 orig_me 设置为原始方法的原始方法条目。断言原始方法的类型不是别名方法,以避免无限循环。

    switch (orig_me->def->type) {
      case VM_METHOD_TYPE_ZSUPER:
        klass = RCLASS_SUPER(klass);
        original_name = orig_me->def->original_id;
        visi = METHOD_ENTRY_VISI(orig_me);
        goto again;
      case VM_METHOD_TYPE_ALIAS:
        visi = METHOD_ENTRY_VISI(orig_me);
        orig_me = orig_me->def->body.alias.original_me;
        VM_ASSERT(orig_me->def->type != VM_METHOD_TYPE_ALIAS);
        break;
      default: break;
    }

    // 处理特殊方法,边界
    // ...... 省略


    // 3)设置 方法 入口
    // 调用 method_entry_set 函数在 target_klass 中为 alias_name 设置别名方法条目
    // 并且设置关联属性
    rb_method_entry_t *alias_me;

    alias_me = method_entry_set(target_klass, alias_name, orig_me, visi, orig_me->owner);
    RB_OBJ_WRITE(alias_me, &alias_me->owner, target_klass);

    if (RB_TYPE_P(target_klass, T_MODULE)) {
        // defined_class should not be set
    }
    else {
        RB_OBJ_WRITE(alias_me, &alias_me->defined_class, orig_me->defined_class);
    }
}
需要 登录 后方可回复, 如果你还没有账号请 注册新账号