First of all, there currently is no #onPreCloseModel
observer callback (although it has been requested,) which a coder would need in order to go and remove observers. The code would also need to keep references to all the observer objects in order to pass to the various #remove_obsever
methods.
Yes, I think it does. When a “watched” object becomes unreferenced by Ruby and marked for GC I think that SketchUp removes that object from it’s callback queues.
In your example, your code is not keeping a reference to the AppObserver
object, instead you are letting the SketchUp observer queue hold the only reference. When the application is in the process of shutting down and has processed all the #onQuit
callbacks, it will clear it’s queue and all the observers will become unreferenced.
But even before this all the model collection observer queues would also be cleaned up and their observers dereferenced.
Now, several things.
You should include the #expectsStartupModelNotifications
callback so that the initial model opened (from a double-click on a skp
file) or created (the new blank model) will get the observers attached.
On Mac it becomes very important to make sure that the model reference points at the correct model object as multiple may be open at the same time.
But, on Windows there is a weird thing where the singleton model reference is reused when the model changes. But the collection references for the new model do get new references. As was explained to us, when you attach the same kind of observer to the same object, it’s previous item in the queue is deleted and a new item is added at the end of the callback queue. (But things could change.) Anyway, we have not needed to worry on Windows about reattaching a model observer to the singleton model
reference.
Where it becomes important to remove observers is when the tasks done or the watching upon the object are temporary. Ie, for an example, say you only need to watch something when a certain native tool is active. (You would use a ToolsObserver
to detect tool changes and attach the temporary observer if the active tool id was a certain value, and remove the observer when it again changes.)
Another example would be if you knew that a component definition was going to be created and you need to do something when it was a valid object. You would temporarily attach a DefinitionsObserver
to react when the next definition was fully created, and then detach the observer.
Anyway, … references must be kept in order to remove observers later. The best place to keep these references, is the instance of the ModelObserver
attached to the model object.
Now, with regard to an extension’s AppObserver
object, … there is only 1 application object … SketchUp itself. So in reality your extension does not need a class object. Classes are used when you need multiple copies (instances) of something.
The various API’s #add_observer
methods do not do any type checking to verify that their arguments are subclasses of anything in particular. The observer mechanism only requires that the object’s callback methods be publicly accessible.
So when you only need 1 code object, a module is the correct kind of object. Since your extension must be within a submodule (of your namespace module,) it makes sense that the extension submodule itself be the singleton AppObserver
object.
Something that many newer API observer coders miss is that “a module” is an instance of class Module
and “a class” is an instance of class Class
, which is a subclass of class Module
. So “a class” and “a module” are very similar kinds of objects, but a class has been given the ability to have multiple instances each holding different state data.
What I’m getting at is that having your extension submodule be it’s own AppObserver
object is not so weird a thing as it is also an instance object.
Example …
module SomeAuthor
module SomeNiftyExtension
extend self
def attach(model)
model.add_observer(ModelSpy.new(model))
end
def expectsStartupModelNotifications
true
end
def onNewModel(model)
attach(model)
end
def onOpenModel(model)
attach(model)
end
def onQuit()
# execute onQuit tasks
end
class ModelSpy < Sketchup::ModelObserver
attr_reader :model
def initialize(model)
@model = model
# Hash to hold observer references:
@spy = {}
# Add all of the implemented observers:
model.definitions.add_observer(@spy[:definitions]= DefinitionsSpy.new)
# ... etc ...
end
# ... ModelObserver callback method definitions ...
end
class DefinitionsSpy < Sketchup::DefinitionsObserver
def onComponentAdded(definitions, definition)
# Do something with the new definition ...
end
end
# .. other observer class definitions ...
# RUN ONCE AT STARTUP:
if !defined?(@loaded)
# Define UI objects here ...
# Attach this module as an AppObserver object:
Sketchup.add_observer(self)
# Mark this extension as loaded:
@loaded = true
end
end # extension submodule
end # namespace module
Another nifty thing is that since the v2016 release all the empty callback methods in the API observer superclasses were deleted so there is really no functionality for observer classes to inherit.
This fact and that also #add_observer
does not type check means that you can create hybrid observers that contain the callback methods for multiple “abstract” observer superclasses.
It makes it much easier to pass data between callback methods if they are in the same class.
I often combine DefinitionsObserver
and DefinitionObserver
in the same class. And sometimes I will wrap all of the collection observers within a hybrid ModelObserver
class. A sometimes when I don’t need separate model state variables I’ll put a mix of callbacks right in the extension submodule and it becomes a singleton observer object attached to whatever I need to attach it to, both permanently and temporarily.