The PERSISTENT option doesn't make the subscription remain active when loading files. It is for ensuring that the subscriber remains registered when remapping ID data. Subscriptions will be removed when loading a new file. Persistent application handler appended to bpy.app.handlers.load_post can be used to call bpy.msgbus.subscribe_rna() when a new file is loaded.
bpy.msgbus.clear_by_owner() requires the owner that you've also passed to bpy.msgbus.subscribe_rna() as argument. It will clear all subscribers by this owner.
Below is an add-on that demonstrates the use of the msgbus. It creates a panel "msgbus" with two operators that allow to register/unregister a handler for the message bus. Once registered, whenever the active object changes, it prints information about it to the terminal/console.

bl_info = {
"name": "Example add-on persistent msgbus",
"author": "Robert Guetzkow",
"version": (1, 0),
"blender": (2, 81, 0),
"location": "View3D > Sidebar > My own addon",
"description": "Example add-on that uses msgbus",
"warning": "",
"wiki_url": "",
"category": "3D View"}
import bpy
from bpy.app.handlers import persistent
For more information see source/blender/python/intern/bpy_msgbus.c
and https://developer.blender.org/P563
Object that will store the handle to the msgbus subscription
subscription_owner = object()
def notification_handler(*args):
print(f"Object: {bpy.context.object.name}, Location: {bpy.context.object.location}, Args: {args}")
def subscribe_active_obj(subscription_owner):
# What the subscription is for, in this example the active object.
# Note that for properties it may be necessary to use path_resolve
# as shown in https://developer.blender.org/P563
subscribe_to = bpy.types.LayerObjects, "active"
# Subscribe to the msgbus to call the handler when the key is modified.
# Please note that not every UI interaction will publish an update,
# despite modifying the key you've subscribed to. For instance, when
# subscribed to `bpy.types.Object, "location"`, changing the location of
# an object through the move tool won't call the handler automatically,
# while updating the location property in the "Tranform" panel in the
# sidebar will. The publishing of an update can be forced through
# `bpy.msgbus.publish_rna()`.
# The "PERSISTENT" option ensures that the subscriber remains registered
# when remapping ID data, it does not mean that the subscription remains
# persistent when loading a different file. This can be accomplished with
# a persistent application handler that is called on file load.
bpy.msgbus.subscribe_rna(
key=subscribe_to,
owner=subscription_owner,
args=("a", "b", "c"),
notify=notification_handler,
options={"PERSISTENT",}
)
# Register the persistent handler, ensures that the subscription will happen
# when a new file is loaded.
if load_handler not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(load_handler)
def unsubscribe_active_obj(subscription_owner):
# Clear all subscribers by this owner
if subscription_owner is not None:
bpy.msgbus.clear_by_owner(subscription_owner)
# Unregister the persistent handler.
if load_handler in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(load_handler)
@persistent
def load_handler(dummy):
subscribe_active_obj(subscription_owner)
class SCENE_OT_msgbus_register_active_obj(bpy.types.Operator):
bl_idname = "scene.msgbus_register_active_obj"
bl_label = "Register Handler"
bl_description = "Register msbus handler for the active object."
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
subscribe_active_obj(subscription_owner)
return {"FINISHED"}
class SCENE_OT_msgbus_unregister_active_obj(bpy.types.Operator):
bl_idname = "scene.msgbus_unregister_active_obj"
bl_label = "Unregister Handler"
bl_description = "Unregister msbus handler for the active object."
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
unsubscribe_active_obj(subscription_owner)
return {"FINISHED"}
class EXAMPLE_PT_panel(bpy.types.Panel):
bl_label = "msgbus"
bl_category = "Active Object"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
layout = self.layout
layout.operator(SCENE_OT_msgbus_register_active_obj.bl_idname)
layout.operator(SCENE_OT_msgbus_unregister_active_obj.bl_idname)
classes = (EXAMPLE_PT_panel,
SCENE_OT_msgbus_register_active_obj,
SCENE_OT_msgbus_unregister_active_obj)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
# Unsubscribe and remove handle
unsubscribe_active_obj(subscription_owner)
if name == "main":
register()
Please note that the message bus doesn't notify for every kind of update.
The message bus system is triggered by updates via the RNA system.
This means that the following updates will result in a notification on
the message bus:
- Changes via the Python API, for example
some_object.location.x += 3.
- Changes via the sliders, fields, and buttons in the user interface.
The following updates do not trigger message bus notifications:
- Moving objects in the 3D Viewport.
- Changes performed by the animation system.
The implementation of the message bus can found in bpy_msgbus.c.