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ść.

Komentarze: Skocz na dół, na górę

  1. Finalizers (a zwłaszcza ObjectSpace#each_object) w Rubym możliwe są tylko dzięki bardzo prymitywnej (i przy okazji bardzo mało wydajnej) implementacji GC (Mark and Sweep), gdzie silnik sobie jeździ po gołej stercie. W Każdym bardziej zaawansowanym systemie GC to jest niemożliwe lub niewydajne (np: Compacting GC, Memcopy GC, itd). W JRubym OS jest czystym narzutem gdzie runtime musi trzymać na boku listę słabych referencji (co już wymusza działanie osobnego wątku oprócz tony niepotrzebnych obiektów). Z tym samym problemem zmagać się będzie Rubinius i to już przy odśmiecaniu młodej generacji, to samo dotyczy IronRuby, Ruby.net, XRuby itd. Prędzej czy później OS wyleci z języka (zwłaszcza gdy w 2.0 dodany zostanie generacyjny GC). Niektóre frameworki do testowania używają OS, lecz zawsze możliwe jest alternatywne rozwiązanie jak np. hooki.

Dodaj komentarz na temat

Zanim skomentujesz...

W komentarzach działają znaczniki Textile.
Zastrzegam sobie prawo do edycji Twojego komentarza tylko i wyłącznie w celach estetycznych (naprawienie źle wstawionego kodu, itp). Nie zmieniam ich treści, ortografii, interpunkcji. Jeśli odczuwasz potrzebę edycji swojego komentarza, skontaktuj się ze mną, a zdziałamy co trzeba.