ruby反射api能够使我们在运行时检测类和对象因此我们下面将会介绍Module, Class, 和Object中定义些思路方法
Module模块有个constants 思路方法它将会返回系统中所有常量名包括类名和模块名nesting 思路方法则是返回当前点上嵌套模块列表.
Ruby代码
list = Math.constants # ["E", "PI"]
Module#ancestors 返回指定类或者模块所有包含类或者模块.
Ruby代码
list = Array.ancestors
# [Array, Enumerable, Object, Kernel]
_variables 思路方法返回给定类和他超类所有类变量个表d_modules 思路方法列出包含在这个类中所有模块
Ruby代码
Parent
@@var1 = nil
end
Child < Parent
@@var2 = nil
end
list1 = Parent._variables # ["@@var1"]
list2 = Array.d_modules # [Enumerable, Kernel]
Class思路方法instance_methods和public_instance_methods 是同义他们返回这个类所有公有思路方法private_instance_methods和 protected_instance_methods 也就是返回私有和保护例子思路方法这几个思路方法都还带有个参数默认是true如果被设置为false超类将不会被搜索
Ruby代码
n1 = Array.instance_methods.size # 121
n2 = Array.public_instance_methods.size # 121
n3 = Array.private_instance_methods.size # 71
n4 = Array.protected_instance_methods.size # 0
n5 = Array.public_instance_methods(falsee).size # 71
Object 类有很多操作例子类似思路方法methods 思路方法将会返回这个对象所有可以被思路方法 public_methods 思路方法将会返回所有公有思路方法他也有个参数来判断是否去父类搜索private_methods, protected_methods,和singleton_methods 也都有类似参数
Ruby代码
SomeClass
def initialize
@a = 1
@b = 2
end
def mymeth
#...
end
protected :mymeth
end
x = SomeClass.
def x.meth
# ...
end
iv = x.instance_variables # ["@b", "@a"]
p x.methods.size # 42
p x.public_methods.size # 41
p x.public_methods(false).size # 1
p x.private_methods.size # 71
p x.private_methods(false).size # 1
p x.protected_methods.size # 1
p x.singleton_methods.size # 1
2 测试栈
有时我们想要知道我们者是谁这个有时是非常有用看下面例子:
Ruby代码
def func1
puts caller[0]
end
def func2
func1
end
func2 # 打印出fucn1在那里被
3 监控执行
个ruby能够内省或者说是检测他自己执行很多都用到了这个功能有兴趣话可以看看这几个ruby源码:debug.rb, profile.rb, 和 tracer.rb
我们能够使用 _trace_func思路方法他接受个block作为参数无论在执行中发生任何事情时这个block都会被我们看下面例子:
Ruby代码
def meth(n)
sum = 0
for i in 1..n
sum i
end
sum
end
_trace_func(proc do |event, file, line,
id, binding, klass, *rest|
prf "%8s %s:%d %s/%s\n", event, file, line,
klass, id
end)
meth(2)
可以看到输出类似这样:
引用
line D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:171 false/
call D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:157 Object/meth
............................
还有个思路方法是Kernel#trace_var它是当全局变量被赋值时才会被自动
假设你想要在外面得到运行轨迹最简单思路方法就是使用tracer 库假设有个prog.rb文件:
Ruby代码
def meth(n)
(1..n).each {|i| puts i}
end
meth(3)
然后我们在命令行load TRacer :
引用
% ruby -r tracer prog.rb
#0:prog.rb:1::-: def meth(n)
#0:prog.rb:1:Module:>: def meth(n)
当源代码执行时每个事件类型都包含"'-'",'>'表示个'<' 表示个返回'C'代表个类'E' 代表结束
4 Traversing the Object Space
ruby运行系统需要保存所有已知对象踪迹(只是为了能够垃圾回收那些没有长时间使用引用也就是gc)这个信息是通过 ObjectSpace.each_object 来得到
Ruby代码
ObjectSpace.each_object do |obj|
prf "%20s: %s\n", obj., obj.inspect
end
如果你指定个类名或者模块名给each_object它就只会返回这种类型对象.
5 使用method_missing
起始和前面const_missing差不多也就是说当你在这个对象上个不存在思路方法时它就会默认method_missing 思路方法:
Ruby代码
CommandWrapper
def method_missing(method, *args)
system(method.to_s, *args)
end
end
cw = CommandWrapper.
cw.date # Sat Apr 28 22:50:11 CDT 2001
cw.du '-s', '/tmp' # 166749 /tmp
在Object中定义method_missing思路方法是默认抛出个异常.
6 跟踪类或者对象改变
我们现在想要写个模块它能够被任何类所包含然后在这个类中每个思路方法都会打印出相应信息比如我们所期待是这样:
Ruby代码
MyClass
Tracing
def one
end
def two(x, y)
end
end
m = MyClass.
m.one # one called. Params =
m.two(1, 'cat') # two called. Params = 1, cat
对于子类我们也是能够跟踪:
Ruby代码
Fred < MyClass
def meth(*a)
end
end
Fred..meth(2,3,4,5) # meth called. Params = 2, 3, 4, 5
下面来看它实现:
Ruby代码
module Tracing
def Tracing.d(o)
o.instance_methods(false).each { |m|
Tracing.hook_method(o, m) }
def o.method_added(meth)
unless @adding
@adding = true
Tracing.hook_method(self, meth)
@adding = false
end
end
end
def Tracing.hook_method(klass, meth)
klass._eval do
alias_method "old_#{meth}", "#{meth}"
_method(meth) do |*args|
puts "#{meth} called. Params = #{args.join(', ')}"
self.send("old_#{meth}",*args)
end
end
end
end
MyClass
Tracing
def first_meth
end
def second_meth(x, y)
end
end
m = MyClass.
p m.first_meth # one called. Params =
p m.second_meth(1, 'cat') # two called. Params = 1, cat
这个代码其实很简单.首先它有两个主思路方法第个是d,它是个回调思路方法当这个模块被插入到个类中时就会这个思路方法在我们上面例子中他做了两件事件是为这个模块类每个思路方法hook_method思路方法第 2件事是为这个类重新定义了 method_added 思路方法这就意味着这个类如果加思路方法话就会这个思路方法也就是说会被检测到
而hook_method实现也是很漂亮使用了_method来动态定义思路方法打印出信息后再使用send来老思路方法
这里还要注意个alias_method 它和alias很类似只不过它只能用在思路方法并且他自己就是个思路方法:
Ruby代码
# Two other ways to write that line...
# Symbols with erpolation:
alias_method :"old_#{meth}", :"#{meth}"
# Strings converted via to_sym:
alias_method "old_#{meth}".to_sym, meth.to_sym
检测个新类思路方法被加到个类或者模块我们能够定义个类思路方法singleton_method_added :
Ruby代码
MyClass
def MyClass.singleton_method_added(sym)
puts "Added method #{sym.to_s} to MyClass."
end
def MyClass.meth1
puts "I'm meth1."
end
end
def MyClass.meth2
puts "And I'm meth2."
end
输出将会是这样子:
引用
Added method singleton_method_added to MyClass.
Added method meth1 to MyClass.
Added method meth2 to MyClass.
inherited 思路方法使用也很类似当个类被子类化就会这个思路方法:
Ruby代码
MyClass
def MyClass.inherited(sub)
puts "#{sub} inherits from MyClass."
end
# ...
end
OtherClass < MyClass
# ...
end
# Output: OtherClass inherits from MyClass.
我们也能够跟踪加个模块例子思路方法到个对象extend_object 思路方法就是做这个:
Ruby代码
module MyMod
def MyMod.extend_object(obj)
puts "Extending object id #{obj.object_id}, #{obj.}"
super
end
# ...
end
x = [1, 2, 3]
x.extend(MyMod)
# Output:
# Extending object id 36491192, type Array
这里要注意super是必须和append_features 重原因样
7 为对象定义个Finalizers
ruby类有构造器可是没有析构器原因很简单就是ruby使用了mark-and-sweep garbage collection 来删除没有引用对象
虽然在ruby中不能真正做到析构器来删除对象这里有个思路方法_finalizer它是当个对象被gc时就会它:
Ruby代码
a = "hello"
puts "The 'hello' has an object id #{a.object_id}"
ObjectSpace._finalizer(a) { |id| puts "Destroying #{id}" }
puts "Nothing to tidy"
GC.start
a = nil
puts "The original is now a candidate for collection"
GC.start
输出结果类似这样:
Ruby代码
The 'hello' has an object id 21089680
Nothing to tidy
The original is now a candidate for collection
Destroying 21089680
当finalizer 被同时对象就被销毁了如果此时你ObjectSpace._id2ref然后参数为刚才那个对象id,则会报个RangeError:
Ruby代码
ObjectSpace._id2ref 21089630 #in `_id2ref': 0x141cd5e is recycled object (RangeError)
由于ruby使用是mark-and-sweep GC 策略那么他就不能保证当结束的前对象什么时候被GC
可是所有切都是不确定在ruby中经常使用block来压缩个源(也就是段代码)使用,在block结尾这个源将会被删除 (也就是说然后其他对象没有任何改变(其实这个也就是说讲代码封装在block里面比较好当block退出时它那些局部变量或者说传进来哪些参数都会被GC掉,因此在ruby1.9里面block里面局部变量和外面变量已经不是同个变量了(假设名字相同))看下面代码:
Ruby代码
File.open("myfile.txt") do |file|
line1 = file.read
# ...
end
在这里当block退出时file 被删除这切都是在open思路方法里控制如果你想要实现个open思路方法子集你可以这么做:
Ruby代码
def File.open(name, mode = "r")
f = os_file_open(name, mode)
block_given?
begin
yield f
ensure
f.close
end
nil
f
end
end
这边使用begin ensure是要做到就算异常被抛出文件也要被关闭.
最新评论