Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
Notifications on the brain. Last time was a bug report about something I stumbled across when debugging some notification-related stuff. Before pursuing that much farther, I figured I’d talk about notifications.
Notifications in Cocoa are a decoupling mechanism. You have an object, say a network monitor, that wants to tell other objects that something happened, such as the network connection went down. There’s a bunch of ways you can implement this kind of behavior.
You could subclass the network listener and override handleDisconnect
. You could set up a target / action for the network monitor to invoke a particular selector on a provided object. You could use the responder chain. You could set up a delegate relationship. You could also use notifications.
Most of the options in that list are 1-to-1, such as there being one delegate or data source that’s used by a table view, or one target/action pair that’s used by an NSButton
.
Notifications are a 1-to-many relationship. Even better, a 1-to-many relationship without a direct connection between the object broadcasting the notification, and the objects that are notified. You could implement an object could with an NSArray
of delegate objects which it walks through to do delegate-like stuff. This object knows exactly who the delegates are. With notifications, the notification center acts as a layer of indirection between the object that’s reporting “Hey! Lost the network connection!” and all the objects that want to react to the network going down:
The three objects on the right (delegate, data viewer, and the logging system) all told the notification center that they were interested in finding out when the network goes down. When it comes time to tell the world that the network went down, the network monitor tells the notification center to tell interested parties. There is no direct connection between the one broadcasting a notification and the ones reading it. Fancy people consider it an implementation of the Observer Pattern.
The notification center acts as an intermediary, freeing objects that want to broadcast a message to the world from keeping their own list of interested observers. It also means that the broadcasting objects don’t have to be of a particular class or adopt a particular interface (which is a popular implementation style in other toolkits). Given Objective-C’s runtime machinery, you can send messages using arbitrary selectors, so the observing objects don’t have to be of particular classes, interfaces, or have to implement a particularly-named callback function.
Objects interested in receiving notifications tell a notification center of their interest. The notification center adds these objects, and their callback information, to an internal list, kind of like an address book. When your program starts, the notification center is pretty empty:
Then an object comes along expressing interest in being notified.
// AppDelegate
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self
selector: @selector(networkByeBye:)
name: kNetworkWentByeByeNotification
object: self.networkMonitor];
The AppDelegate
is telling the notification center that it wants to know when the network goes away, perhaps so it can schedule a reconnect in the future. In particular, the AppDelegate
is interested in a notification named kNetworkWentByeByeNotification
, which is just an NSString
. If a notification by that name gets posted by the networkMonitor, send the message networkByeBye:
to self (the AppDelegate
). The notification center jots down the address of the app delegate, the address of the network monitor, as well as the selector, and sticks it under a rock labeled kNetworkWentByeByeNotification
Then another object comes along, this time the data view. It wants to know when the network goes down so it can update the user interface. It registers interest in a similar way:
// DataViewer
[center addObserver: self
selector: @selector(handleNetworkChange:)
name: kNetworkWentByeByeNotification
object: nil];
Same kind of drill with the app delegate. This time it specifies a nil
object. This means that no matter who posts the network-bye-bye notification, the data viewer will get it. The AppDelegate
, though, will only get bye-bye notifications from a specific network monitor.
The notification center grabs another piece of paper, jots down the address of the data viewer, the name of the selector to trigger, and the filter for the broadcasting object (in which case is nil
), and hides it under the kNetworkWentByeByeNotification
rock:
Finally, the logging system wants to register itself. This developer used a more modern API, using a block and an operation queue:
// LogOTron
// _token is an instance variable of type 'id'
_token = [center addObserverForName: kNetworkWentByeByeNotification
object: nil
queue: [NSOperationQueue mainQueue]
usingBlock: ^(NSNotification *notification) {
NSLog (@"Network went down: %@", notification);
}];
This tells the notification center, “hey, whenever someone posts the bye-bye notification, I want you to toss this block onto this queue.” The notification center tears off another piece of paper and makes some notes, in particular the object to filter notifications with (in this case, nil
means all objects posting the notification), the queue to run the blocks on, and the block itself. It hides this note under the kNetworkWentByeByeNotification
rock as well.
One interesting consequence of this newer call is that you can register a notification handler without having a corresponding class and object. The old-school API sends a message to an object. If you wanted to observe something you had to have some object the notification center could send a message to. With the block API, you just give it a block. No need for a supporting object.
Everyone is now registered. Has anything actually happened? Not yet. The notification center just has a list of interested parties, but they haven’t been told anything. The action starts when someone posts a notification.
The network goes down. The network monitor wants everybody to know:
// NetworkMonitor
- (void) networkWentDown {
// Collect the error
// Pack the error and other information into a dictionary
NSDictionary *userInfo = ...;
// Post notification.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName: kNetworkWentByeByeNotification
object: self
userInfo: userInfo];
} // networkWentDown
The last call tells the notification center to post the network bye-bye notification to all interested parties. The notification center looks under the rock labeled kNetworkWentByeByeNotification
and looks at each of the pieces of paper it hid under there. If the object should be notified, the given selector is used to send a message to the object, or the block is put on a queue.
The AppDelegate
will get notified by having its networkByeBye:
method called. The LogOTron
will get notified by having its block scheduled onto the main queue.
The DataView
will get notified via its handleNetworkChange:
method only if the network monitor posting the notification is the same (which is an identity check, not an equality check).
When an object is no longer interested in receiving notifications, it should unregister itself from the notification center. The notification center does not retain the observer object and the filtering object, so it’s possible for the observer object to be destroyed and the notification center holding on to a dangling reference. It does retain the block when using the block based API, so watch out for retain cycles (more about that later). You should unregister yourself as soon as you know you’re not interested in the notification. You can also unregister in your dealloc so that there are no dangling references to your object.
It’s pretty easy to unregister from all notifications:
[center removeObserver: self];
This removes self from the notification center completely. For maximal safety, though, you should only unregister from the specific notifications you registered yourself for. There may be some other notifications that the object is listening to deep inside of Cocoa.
In the case of the AppDelegate
and the DataViewer
, they would call something
[center removeObserver: self
name: kNetworkWentByeByeNotification
object: nil];
This unhooks them from any bye-bye notifications, but leaves the rest of the notifications untouched.
The LogOTron
case is a little more complicated. Because the notification doesn’t require a listening object, there’s not really a way to tell the notification center what it needs to remove from its stack of objects to notify. There’s nothing to grab on to. That’s why the block-based addObserverForName
method returns a token. This is an opaque object to stash somewhere, and then pull it out when you need to unregister yourself from the notification.
Therefore, LogOTron
would do something like this to unregister itself, using the token returned when the notification was registered:
[center removeObserver: _token];
(tune in next week when notification handling itself is covered, along with spying and some gotchas related to threading)
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...