Я пытаюсь получить обратный вызов, когда любой метод на конкретном классе называют. Переопределение "отправляет", не работает. Это кажется, отправляют, не становится названным в нормальном вызове метода Ruby. Возьмите следующий пример.
class Test
def self.items
@items ||= []
end
end
Если мы переопределяем, отправляют на Тесте, и затем называют Test.items, отправляют, не становится названным.
То, что я пытаюсь сделать возможный?
Я не использовал бы set_trace_func, так как он, вероятно, значительно замедлит вещи.
Примерно так: работает с методами экземпляра и методами класса, он будет перехватывать не только текущие методы, определенные в классе, но и любые, которые будут добавлены позже при повторном открытии класс и т. д.
(есть также rcapture http://code.google.com/p/rcapture/ ):
module Interceptor
def intercept_callback(&block)
@callback = block
@old_methods = {}
end
def method_added(my_method)
redefine self, self, my_method, instance_method(my_method)
end
def singleton_method_added(my_method)
meta = class << self; self; end
redefine self, meta, my_method, method(my_method)
end
def redefine(klass, me, method_name, my_method)
return unless @old_methods and not @old_methods.include? method_name
@old_methods[method_name] = my_method
me.send :define_method, method_name do |*args|
callback = klass.instance_variable_get :@callback
orig_method = klass.instance_variable_get(:@old_methods)[method_name]
callback.call *args if callback
orig_method = orig_method.bind self if orig_method.is_a? UnboundMethod
orig_method.call *args
end
end
end
class Test
extend Interceptor
intercept_callback do |*args|
puts 'was called'
end
def self.items
puts "items"
end
def apple
puts "apples"
end
end
class Test
def rock
puts "rock"
end
end
Test.items
Test.new.apple
Test.new.rock
Используйте псевдоним
или alias_method
:
# the current implementation of Test, defined by someone else
# and for that reason we might not be able to change it directly
class Test
def self.items
@items ||= []
end
end
# we open the class again, probably in a completely different
# file from the definition above
class Test
# open up the metaclass, methods defined within this block become
# class methods, just as if we had defined them with "def self.my_method"
class << self
# alias the old method as "old_items"
alias_method :old_items, :items
# redeclare the method -- this replaces the old items method,
# but that's ok since it is still available under it's alias "old_items"
def items
# do whatever you want
puts "items was called!"
# then call the old implementation (make sure to call it last if you rely
# on its return value)
old_items
end
end
end
Я переписал ваш код, используя синтаксис class << self
чтобы открыть метакласс, потому что я не уверен, как в противном случае использовать alias_method
в методах класса.
Вы можете сделать что-то подобное, вы даже можете поставить условия на вызываемый метод или нет (я не думаю, что это полезно, но все же у вас это есть на всякий случай).
module MethodInterceptor
def self.included(base)
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
# we declare the method_list on the class env
@_instance_method_list = base.instance_methods.inject(Hash.new) do |methods, method_name|
# we undef all methods
if !%w(__send__ __id__ method_missing class).include?(method_name)
methods[method_name.to_sym] = base.instance_method(method_name)
base.send(:undef_method, method_name)
end
methods
end
end
end
module ClassMethods
def _instance_method_list
@_instance_method_list
end
def method_added(name)
return if [:before_method, :method_missing].include?(name)
_instance_method_list[name] = self.instance_method(name)
self.send(:undef_method, name)
nil
end
end
module InstanceMethods
def before_method(method_name, *args)
# by defaults it always will be called
true
end
def method_missing(name, *args)
if self.class._instance_method_list.key?(name)
if before_method(name, *args)
self.class._instance_method_list[name].bind(self).call(*args)
else
super
end
else
super
end
end
end
end
class Say
include MethodInterceptor
def before_method(method_name, *args)
# you cannot say hello world!
return !(method_name == :say && args[0] == 'hello world')
end
def say(msg)
puts msg
end
end
Надеюсь, это сработает.
вы пытаетесь подключить метод экземпляра класса? Тогда следующий фрагмент может помочь. Он использует RCapture, который можно установить через
gem install rcapture
. Вводную статью можно найти по адресу здесь
require 'rcapture'
class Test
include RCapture::Interceptable
end
Test.capture_post :class_methods => :items do
puts "items!"
end
Test.items
#=> items!
Да, оба этих образца кода ведут себя одинаково путь в параллельной среде. Изменчивые поля никогда не кэшируются локально , поэтому после того, как один поток вызывает update (), который заменяет список новым списком, затем get () во всех других потоках возвращает новый список.
Но если у вас есть код, который использует его так:
list = get()
list = list.add(something) // returns a new immutable list with the new content
update(list)
то он не будет работать так, как ожидалось на любом из этих примеров кода (если два потока делают это параллельно, то изменения, сделанные одним из них, могут быть перезаписаны другим). Но если только один поток обновляет список, или новое значение не зависит от старого значения, то никаких проблем.
-121--3338956-Рекомендуется использовать делегата от контроллера модального вида обратно к контроллеру вида, открывшему вид. Ознакомьтесь с официальными документами для получения примеров.
Причина того, что это рекомендуемый способ, заключается в том, что ViewController, который первоначально запустил модал, также будет управлять его отменой.
Это действительно просто сделать и думать более элегантно, чем использовать viewWillanDispear - как есть и другие причины, почему вид может исчезнуть!
Создайте протокол на модальном ViewController - xViewControleyDelegate
@protocol xViewControllerDelegate
- (void) modalDialogFinished;
@end
Затем при определении родительского контроллера представления сделайте родительскую реализацию делегата с помощью < xViewControlterDelegate >
.
В родительском контроллере представления будет установлен метод modalDialogFinished, который может обрабатывать отклоненную команду и обновление и т. д.
Не забудьте передать id < xViewControleyDelegate >
контроллеру модального представления в коде init и сохранить его в качестве поля объекта.
Когда вы хотите к disssmiss свою модальную точку зрения тогда, вы просто должны сослаться на delegate.modalDialogFinished.
Если это не имеет смысла, то я могу указать вам на какой-то лучший пример кода - но я надеюсь, что использование делегатов не ново для вас.
UPDATE::
Вот официальная документация Apple о том, как это сделать для контроллера модального вида:
-121--1713811- Вы можете увидеть, как это делается с помощью функции крючка ExtLib. ExtLib:: Hook в основном позволяет вызывать произвольные обратные вызовы до или после завершения метода. См. код на GitHub здесь, чтобы узнать, как он выполнен (он переопределяет : метод _ добавлен
для автоматической перезаписи методов по мере их добавления в класс).
У меня нет полного ответа, но я думаю, что method_added может быть здесь полезным.
RCapture делает то, что вы хотите: http://cheind.wordpress.com/2010/01/07/introduction-rcapture/
Я заставил его работать с помощью класса Proxy - а затем установить константу, используя имя реального класса. Хотя я не знаю, как заставить его работать с экземплярами. Есть ли способ изменить, на какие объектные переменные тоже указывают?
В принципе, я хочу это сделать:
t = Test.new
Persist.new(t)
t.foo # invokes callback
Вот код, который я использовал, чтобы заставить его работать с классами:
class Persist
class Proxy
instance_methods.each { |m|
undef_method m unless m =~ /(^__|^send$|^object_id$)/
}
def initialize(object)
@_persist = object
end
protected
def method_missing(sym, *args)
puts "Called #{sym}"
@_persist.send(sym, *args)
end
end
attr_reader :object, :proxy
def initialize(object)
@object = object
@proxy = Proxy.new(@object)
if object.respond_to?(:name)
silence_warnings do
Object.const_set(@object.name, @proxy)
end
end
end
end