NSNotification &NSNotificationCenter
Any idea is inextricably linked to how it’s communicated. A medium defines the form and scale of significance in such a way to shape the very meaning of an idea. Very truly, the medium is the message.
One of the first lessons of socialization is to know one’s audience. Sometimes communication is one-to-one, like an in-person conversation, while at other times, such as a television broadcast, it’s one-to-many. Not being able to distinguish between these two circumstances leads to awkward situations.
This is as true of humans as it is within a computer process. In Cocoa, there are a number of approaches to communicating between objects, with different characteristics of intimacy and coupling:
Audience | |||
---|---|---|---|
Intimate (One-to-One) | Broadcast (One-to-Many) | ||
Coupling | Loose |
|
|
Strong |
|
|
We’ve discussed the importance of how events are communicated in APIs previously in our article on Key-Value Observing. This week, we’ll expand our look at the available options, with NSNotification
& NSNotification
.
NSNotification
provides a centralized hub through which any part of an application may notify and be notified of changes from any other part of the application. Observers register with a notification center to respond to particular events with a specified action. Each time an event occurs, the notification goes through its dispatch table, and messages any registered observers for that event.
Each running Cocoa program manages its own default notification center, so it’s unusual for a new notification center to be instantiated separately.
Each NSNotification
object has a name
, with additional context optionally provided by an associated object
and user
dictionary.
For example, UIText
posts an NSNotification
with the name UIText
each time its text changes. The object associated with that notification is the text field itself. In the case of UIKeyboard
, frame positioning and animation timing are passed in user
, while the notification’s associated object
is nil
.
Adding Observers
All sorts of notifications are constantly passing through NSNotification
.* But like a tree falling in the woods, a notification is moot unless there’s something listening for it.
The traditional way to add an observer is –add
, in which an object (usually self
) adds itself to have the specified selector performed when a matching notification is posted.
The modern, block-based API for adding notification observers is –add
. Instead of registering an existing object as an observer for a notification, this method creates its own anonymous object to be the observer, which performs a block on the specified queue (or the calling thread, if nil
) when a matching notification is posted. Unlike its similarly named @selector
-based counterpart, this method actually returns the constructed observer object, which is necessary for unregistering the observer, as discussed in the next section.
Contrary to a recent article claiming otherwise,
–add
should not be considered harmful. It’s perfectly safe and suitable for use in applications. Just make sure to understand memory management rules when referencingObserver For Name:object:queue:using Block: self
in blocks. Any concerns in this respect are the same as for any other block-based API.
The name
and object
parameters of both methods are used to decide whether the criteria of a posted notification match the observer. If name
is set, only notifications with that name will trigger, but if nil
is set, then all names will match. The same is true of object
. So, if both name
and object
are set, only notifications with that name and the specified object will trigger. However, if both name
and object
are nil
, then all notifications posted will trigger.
*See for yourself! An ordinary iOS app fires dozens of notifications just in the first second of being launched—many that you’ve probably never heard of before, nor will ever have to think about again.
let center = NSNotification Center.default Center()
center.add Observer For Name(nil, object: nil, queue: nil) { notification in
print("\(notification.name): \(notification.user Info ?? [:])")
}
NSNotification Center *center = [NSNotification Center default Center];
[center add Observer For Name:nil
object:nil
queue:nil
using Block:^(NSNotification *notification)
{
NSLog(@"%@", notification.name);
}];
Removing Observers
It’s important for objects to remove observers before they’re deallocated, in order to prevent further messages from being sent.
There are two methods for removing observers: -remove
and -remove
. Again, just as with adding observers, name
and object
are used to define scope. -remove
, or -remove
with nil
for both parameters, will remove the observer from the notification center dispatch table entirely, while specifying parameters for -remove
will only remove the observer for registrations with that name and/or object.
Posting Notifications
Of course, consuming is but one side of the story. In addition to subscribing to system-provided notifications, applications may want to publish and subscribe to their own.
Notifications are created with +notification
.
Notification names are generally defined as string constants. Like any string constant, it should be declared extern
in a public interface, and defined privately in the corresponding implementation. It doesn’t matter too much what a notification name’s value is defined to be; the name of the variable itself is commonplace, but a reverse-DNS identifier is also a classy choice. So long as notification names are unique (or explicitly aliased), everything will work as expected.
Keys for user
should likewise be defined as string constants. It’s important to clearly document the expected kinds of values for each key, since the compiler can’t enforce constraints on dictionaries the same way it can for an object.
class Foo Controller : UIView Controller {
enum Notifications {
static let Foo Did Bar = "XXFoo Did Bar Notification"
static let Foo Did Bazoom = "XXFoo Did Bazoom Notification"
}
…
}
// Foo.h
extern NSString * const XXFoo Did Bar Notification;
// Foo.m
NSString * const XXFoo Did Bar Notification = @"XXFoo Did Bar Notification";
Notifications are posted with –post
or its convenience method –post
, which passes nil
for user
. –post
is also available, but it’s generally preferable to have the notification object creation handled by the method itself.
Recall from the previous section how name
and object
act to scope notification dispatch. Developers are advised to be consistent in how objects are posted with notifications, and to have this behavior documented clearly in the public interface.
Since notification dispatch happens on the posting thread, it may be necessary to dispatch_async
to dispatch_get_main_queue()
so that a notification is handled on the main thread. This is not usually necessary, but it’s important to keep in mind.
KVO != NSNotificationCenter
Something that often slips up developers is how similar the method signatures for Key-Value Observing are to those of NSNotification
:
Key-Value Observing
func add Observer(observer: NSObject, for Key Path key Path: String,
options: NSKey Value Observing Options,
context: Unsafe Mutable Pointer<Void>)
- (void)add Observer:(NSObject *)observer
for Key Path:(NSString *)key Path
options:(NSKey Value Observing Options)options
context:(void *)context
NSNotificationCenter
func add Observer(observer: Any Object,
selector a Selector: Selector,
name a Name: String?,
object an Object: Any Object?)
func add Observer For Name(name: String?,
object obj: Any Object?,
queue: NSOperation Queue?,
using Block block: (NSNotification) -> Void) -> NSObject Protocol
- (void)add Observer:(id)notification Observer
selector:(SEL)notification Selector
name:(NSString *)notification Name
object:(id)notification Sender
- (id)add Observer For Name:(NSString *)name
object:(id)obj
queue:(NSOperation Queue *)queue
using Block:(void (^)(NSNotification *))block
Key-Value Observing adds observers for keypaths, while NSNotificationCenter adds observers for notifications. Keep this distinction clear in your mind, and proceed to use both APIs confidently.
Notifications are an essential tool for communicating across an application. Because of its distributed, low-coupling characteristics, notifications are well-suited to a wide range of architectures. APIs would do well to add them for any significant events that might benefit from greater circulation—the performance overhead for this sort of chattiness is negligible.
As it were, thinking about notifications in your own life can do wonders for improving your relationships with others. Communicating intent and giving sufficient notice are the trappings of a mature, grounded individual.
…but don’t take that advice too far and use it to justify life-streaming, or anything. Seriously, stop taking pictures, and just eat your damn food, amiright?