Destruktory w Rubim, czyli ObjectSpace w akcji

Posted by Jacek Galanciak on

Ruby (podobnie jak Java) nie posiada destruktorów. Może to się wydawać dziwne, ale okazuje się, że garbage collector załatwia za nas większość przykrych czynności, a pozostałe można zrealizować poprzez odpowiednią budowę aplikacji.

A co, jeśli się uprzemy na destruktor lub po prostu metodę, która zostanie wykonana podczas niszczenia obiektu?

“Destruktory” dla egzemplarza

one = "jeden"
two = "dwa"
three = "trzy"

ObjectSpace.define_finalizer(one, lambda {|id| puts "\"one\" (#{id}) umarł w męczarniach" })
ObjectSpace.define_finalizer(two, lambda do |id|
                                    puts "\"two\" (#{id}) umarł w męczarniach"
                                    end)

"two" (22660860) umarł w męczarniach
"one" (22660870) umarł w męczarniach

Trzeci też umarł, ale bardziej po cichu.

“Destruktory” dla klasy

Należy tutaj pamiętać, że definiujemy metodę klasy, a nie instancji.

class TestClass
  def initialize
    ObjectSpace.define_finalizer(self, self.class.method(:finish_him!).to_proc)
  end
  def TestClass.finish_him!(id)
    puts "Flawless victory (#{id})"
  end
end

test = TestClass.new

Wymuszenie garbage collectora

Moduł ObjectSpace to nic innego jak interakcja z garbage collectorem, który działa w pełni poprawnie, ale automatycznie - nigdy nie wiemy, kiedy zostanie wywołany. Możemy jednak wywołać metodę garbage_collect, by bardziej kontrolować pracę kolektora. Niestety, niszczymy wówczas wszystkie nieużywane obiekty, ale to chyba nie jest duży (o ile w ogóle) mankament.

class TestClass
  def initialize
    ObjectSpace.define_finalizer(self, self.class.method(:finish_him!).to_proc)
  end
  def TestClass.finish_him!(id)
    puts "Flawless victory (#{id})"
  end
end

def create_garbage
  TestClass.new
end

puts "Tworzę śmieci"
create_garbage
puts "Usuwam śmieci."
ObjectSpace.garbage_collect
puts "Gotowe."
Tworzę śmieci
Usuwam śmieci.
Flawless victory (22660260)
Gotowe.

Warto pamiętać, że sprzątaniu ulegną śmieci powstałe w innych blokach niż ten, w którym poprzez wymuszenie wywoływany jest kolektor.

Pozostaje tylko pytanie: po co wymuszać? Jest to uzasadnione w naprawdę niewielu przypadkach. Innymi słowy: łapy precz. To tylko ciekawostka ;).

Ups…

ObjectSpace posiada metodę _id2ref, która zwraca referencję obiektu o podanym id.

ObjectSpace._id2ref("test".object_id) # => "test"

Kiedy wywoływany jest finalizer, przekazywany jest mu id obiektu, który właśnie został zniszczony. Szukanie referencji po takim identyfikatorze nie zadziała, bo obiektu już prawdopodobnie nie ma. Warto o tym pamiętać, by uniknąć frustrujących błędów na przyszłość.