可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I need to occasionaly create images with rmagick in a cache dir.
To then get rid of them fast, without loosing them for the view, I want to delete the image-files while my Ruby Instance of the Image-Class get's destructed or enters the Garbage Collection.
What ClassMethod must I overwrite to feed the destructor with code?
回答1:
You can use ObjectSpace.define_finalizer
when you create the image file, and it will get invoked when the garbage man comes to collect. Just be careful not to reference the object itself in your proc, otherwise it won't be collected by the garbage man. (Won't pick up something that's alive and kicking)
class MyObject
def generate_image
image = ImageMagick.do_some_magick
ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
end
end
回答2:
@edgerunner's solution almost worked. Basically, you cannot create a closure in place of the define_finalizer
call since that captures the binding of the current self
. In Ruby 1.8, it seems that you cannot use any proc
object converted (using to_proc
) from a method that is bound to self
either. To make it work, you need a proc
object that doesn't capture the object you are defining the finalizer for.
class A
FINALIZER = lambda { |object_id| p "finalizing %d" % object_id }
def initialize
ObjectSpace.define_finalizer(self, self.class.method(:finalize)) # Works in both 1.9.3 and 1.8
#ObjectSpace.define_finalizer(self, FINALIZER) # Works in both
#ObjectSpace.define_finalizer(self, method(:finalize)) # Works in 1.9.3
end
def self.finalize(object_id)
p "finalizing %d" % object_id
end
def finalize(object_id)
p "finalizing %d" % object_id
end
end
a = A.new
a = nil
GC.start
回答3:
GC quirks are nice to read about, but why not properly deallocate resources according to already existing language syntax?
Let me clarify that.
class ImageDoer
def do_thing(&block)
image= ImageMagick.open_the_image # creates resource
begin
yield image # yield execution to block
rescue
# handle exception
ensure
image.destruct_sequence # definitely deallocates resource
end
end
end
doer= ImageDoer.new
doer.do_thing do |image|
do_stuff_with_image # destruct sequence called if this throws
end # destruct_sequence called if execution reaches this point
Image is destroyed after the block finishes executing. Just start a block, do all the image processing inside, then let the image destroy itself. This is analogous to the following C++ example:
struct Image
{
Image(){ /* open the image */ }
void do_thing(){ /* do stuff with image */ }
~Image(){ /* destruct sequence */ }
};
int main()
{
Image img;
img.do_thing(); // if do_thing throws, img goes out of scope and ~Image() is called
} // special function ~Image() called automatically here
回答4:
Ruby has ObjectSpace.define_finalizer
to set finalizers on objects, but its use isn't exactly encouraged and it's rather limited (e.g. the finalizer can't refer to the object it is set for or else the finalizer will render the object ineligible for garbage collection).
回答5:
There's really no such thing as a destructor in Ruby.
What you could do is simply clear out any files that are no longer open, or use the TempFile class which does this for you.
Update:
I previously claimed that PHP, Perl and Python do not have destructors, but this does appear to be false as igorw points out. I have not seen them used very often, though. A properly constructed destructor is essential in any allocation-based language, but in a garbage collected one it ends up being optional.
回答6:
There is very simple solution for your problem. Ruby design encourage you to do all actions in definite and clear way. No need for magic actions in constructor/destructor. Yes, constructors are required as a convenient way to assign initial state of object but not for "magic" actions. Let me illustrate this approach on possible solution.
Goal, to keep image objects available but clean cache files of images.
# you are welcome to keep an in memory copy of the image
# GC will take care of it.
class MyImage
RawPNG data
end
# this is a worker that does operations on the file in cache directory.
# It knows presizely when the file can be removed (generate_image_final)
# no need to wait for destructor ;)
class MyImageGenerator
MyImage @img
def generate_image_step1
@image_file = ImageLib.create_file
end
def generate_image_step2
ImageLib.draw @image_file
end
def generate_image_final
@img=ImageLib.load_image @image_file
delete_that_file @image_file
end
def getImage
# optional check image was generated
return @img
end
end
回答7:
To implement something similar to Python's context manager in Ruby:
#!/usr/bin/env ruby
class Customer
@@number_of_customers = 0
def initialize(id, name)
@_id = id
@_name = name
@@number_of_customers += 1
end
def self.get_number_of_customers()
return @@number_of_customers
end
def get_id()
return @_id
end
def get_name()
return @_name
end
def finalize()
@@number_of_customers -= 1
end
end
class Manager
def self.manage_customer(*custs, &block)
yield custs
custs.each do |c|
c.finalize()
end
end
end
Manager.manage_customer(Customer.new(0, 'foo'), Customer.new(1, 'bar')) do |custs|
puts("id is #{custs[0].get_id()}")
puts("id is #{custs[1].get_id()}")
puts("name is #{custs[0].get_name()}")
puts("name is #{custs[1].get_name()}")
puts("number of customers is #{Customer.get_number_of_customers()}")
end
puts("number of customers is #{Customer.get_number_of_customers()}")
In summary, what's going on here is that Manager is similar to using Python's with keyword. Manager is a high level class that receives Customer objects from the client, yields them back out, and explicitly destroys them at the end of its scope when the client is done using them (which is implicit from the client's perspective).