Skip to content

UXNotificationCenter

UXNotificationCenter is a neutral publish/subscribe bus, shaped like NSNotificationCenter. One object posts a named notification, and any number of others are called back. The observers need not know the poster exists.

It is the decoupled sibling of target/action: an action goes from one sender to one known receiver; a notification goes from one sender to N unknown receivers.

#use <UXKit> // or #import "UXNotificationCenter.xc"
UXNotificationCenter* nc = UXNotificationCenter.shared();
nc.addObserver((Object*)self, &self.onChanged, (u8*)"doc.changed", (Object*)0);
nc.postWith((u8*)"doc.changed", (Object*)0, 42, 7);
void onChanged(UXNotification* note) {
Stdio.printf("%s a=%d b=%d\n", note.name, note.a, note.b);
}

Posting a name nobody observes is not an error; it is not delivered. A library can post freely without knowing whether anyone is listening.

A naive observer list keeps its observers alive, which creates a retain cycle: window → centre → observer → window. The centre therefore owns neither half of a registration:

  • the observer is held weak:
  • the callback never owns its receiver

A dead observer’s callback reads false, so dispatch skips it and the next sweep prunes the entry.

The notification’s object is strong, deliberately

Section titled “The notification’s object is strong, deliberately”

UXNotification.object (the poster) is a strong reference, like NSNotification’s. A notification is transient (created, delivered and discarded within one post), so it cannot form a cycle and nothing needs to stay alive beyond the call.

It must not be weak. A weak register/unregister on the sender for every post churns the sender’s weak list, which accumulates and eventually corrupts. The symptom is a crash inside the runtime’s weak bookkeeping, far from the post that caused it.

The fourth argument to addObserver is the object to hear from:

nc.addObserver((Object*)w, &w.onChanged, (u8*)"doc.changed", (Object*)docA);
objectmeaning
0any sender — every post of that name
an objectonly posts whose sender is that object

An observer registered with 0 hears posts from every sender, including ones a filtered observer ignores. Both kinds coexist on the same name. A document-per-window app uses this: a global status line listens to all documents, and each window listens only to its own.

shared · addObserver · removeObserver · post · postWith · postNotification

static UXNotificationCenter* shared(void)

The process-wide centre, made on first use. It has the same lazy-singleton shape as UXNull.null() and UXLog.shared().

You can also make your own centre for a private bus.

void addObserver(Object* observer, callback method void(UXNotification* note),
u8* name, Object* object)

Subscribes. observer is held weakly and is what removeObserver matches on; method is the callback; name is the notification; object filters by sender (see above).

void removeObserver(Object* observer)

Drops every registration for that observer.

void post(u8* name, Object* object)

Posts with no payload.

void postWith(u8* name, Object* object, i32 a, i32 b)

Posts with a small generic payload. a and b are two integers, which cover most toolkit notifications (a resize carries width and height) without allocating a userInfo dictionary for every post.

When two integers are not enough, define a notification whose object is the thing being announced, and let the observer ask it.

void postNotification(UXNotification* n)

Posts a pre-built UXNotification, when you are forwarding one or want to construct it yourself.

post 'doc.changed':
left heard 'doc.changed' (a=42 b=7)
right heard 'doc.changed' (a=42 b=7)
post 'doc.saved' (no observers):
post from a DIFFERENT sender:
left heard 'doc.changed' (a=1 b=1)
right heard 'doc.changed' (a=1 b=1)
post from the watched sender:
left heard 'doc.changed' (a=2 b=2)
right heard 'doc.changed' (a=2 b=2)
watch heard 'doc.changed' (a=2 b=2)
after removeObserver(right):
left heard 'doc.changed' (a=9 b=9)

left and right registered with object == 0, so they hear every post; watch registered against one sender and hears only that one. The program is website/site/examples/uxkit/notify.xc and the doc-examples gate compiles it.

  • A plain class (not an Object subclass)