Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
Last time you saw how to register for notifications. Now time to handle them!
With the classic notification registration API, you specify an object to be sent a message, and the selector to use. That message can take at most one argument, which is a pointer to an NSNotification
object.
So, given this registration:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self
selector: @selector(networkByeBye:)
name: kNetworkWentByeByeNotification
object: self.networkMonitor];
The resulting handler method would look like this:
- (void) networkByeBye: (NSNotification *) notification {
// handle the notification
} // networkByeBye
When you register for notifications using a block, you supply a queue, and a block to schedule on that queue when the notification is posted. Because the code is in the block there’s no need to implement a method, or even have an object involved. Of course, if a lot of work is being done in the handler, you’ll want to factor it out into its own method or function.
For completeness, here’s an example from last time. The notification block takes an NSNotification
object:
_token = [center addObserverForName: kNetworkWentByeByeNotification
object: nil
queue: [NSOperationQueue mainQueue]
usingBlock: ^(NSNotification *notification) {
NSLog (@"Network went down: %@", notification);
}];
Recall that the token is an opaque object that you can use to unregister the notification later.
So, about that notification object. That’s how the code posting the notification “Hey! The network just went down” communicates with the code handling the notification. “Oh dear, I need to redraw my view.”
There’s three pieces of information baked into the notification object:
The notification name is an NSString
that’s used to broadcast the notification, in the code blocks above, the name would be kNetworkWentByeByeNotification
. Sometimes this might be all the information you need to convey. “This thing happened.”
You can also use the notification name to disambiguate multiple notifications that call the same method. There might be a large body of common code that runs for each notification (perhaps for network-connected and also for network-disconnected), but then a little bit of extra code for just one of the cases (update a timestamp label when the network goes away):
- (void) networkChanged: (NSNotification *) notification {
// do stuff
if ([notification.name isEqualToString: kNetworkWentByeByeNotification]) {
NSLog (@"Network went away");
// update timestamp label
}
} // networkChanged
The object associated with the notification is the second piece of information held by the NSNotification
object. This is provided by the code that posts the notification:
// NetworkMonitor
[center postNotificationName: kNetworkWentByeByeNotification
object: self
userInfo: userInfo];
Say you had a view class that broadcast when it was tapped. “Yo! I was tapped!”. And you put two of them on screen. One implementation approach would be to have two different methods (or blocks) that handle the notifications. (This is actually the approach I’d take). You could also have a single notification handler that discriminated on the object:
- (void) tapcasterTapped: (NSNotification *) notification {
if (notification.object == self.shoesizeTapcaster) {
// Upload the shoe size.
} else if (notification.object == self.bloodtypeTapcaster) {
// Take a blood sample from the user.
}
} // tapcasterTapped
The third piece of information is the userInfo, a dictionary containing arbitrary key/value pairs that the notification poster decided would be of interest to anyone receiving the notification. There are no predefined conventions about what gets passed in the userInfo, so you can do whatever you want. For the network monitor, it could include such information like the exact network interface that went down, the time it went down, the IP address and port it was using, the time since the last connection, and the Bonjour name used to advertise a service.
It’s good form to provide symbolic constants for the keys:
extern NSString *const kNetworkInterfaceKey;
extern NSString *const kNetworkDownTimestampKey;
extern NSString *const kNetworkAddressKey;
extern NSString *const kNetworkPortKey;
extern NSString *const kNetworkUptimeSecondsKey;
extern NSString *const kNetworkBonjourKey;
That way you eliminate “typos” as a cause for programmer pain and suffering.
Then when posting the notification, cook up a user info dictionary:
NSDictionary *userInfo =
[NSDictionary dictionaryWithObjectsAndKeys:
self.interface, kNetworkInterfaceKey,
[NSDate date], kNetworkDownTimestampKey,
self.connectionAddress, kNetworkAddressKey,
self.connectionPort, kNetworkPortKey,
[NSNumber numberWithInt: timeDelta], kNetworkUptimeSecondsKey,
self.bonjourName, kNetworkBonjourKey,
nil];
// Post notification.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName: kNetworkWentByeByeNotification
object: self
userInfo: userInfo];
(Objective-C literals will make this look soooo much nicer)
And then you can pick out the information you want when handling the notification:
NSString *interface = [notification.userInfo objectForKey: kNetworkInterfaceKey];
NSLog (@"interface is %@", interface);
Like absolutely everything else involved in programming, you can apply rules and conventions to stuff that doesn’t necessarily need it.
When I declare notifications and keys, I typically declare them together, with the keys that go along with a notification underneath the notification:
extern NSString *const kNetworkWentByeByeNotification;
extern NSString *const kNetworkInterfaceKey;
extern NSString *const kNetworkDownTimestampKey;
extern NSString *const kNetworkAddressKey;
extern NSString *const kNetworkPortKey;
extern NSString *const kNetworkUptimeSecondsKey;
extern NSString *const kNetworkBonjourKey;
Suffixing the constant with “Notification” and “Key” show how they relate to each other.
For the actual string values, there’s (at least) two camps. And I waffle between the two.
One idea is to have keys be very readable:
NSString *const kNetworkWentByeByeNotification = @"network went bye bye";
NSString *const kNetworkInterfaceKey = @"network interface";
NSString *const kNetworkDownTimestampKey = @"network down timestamp";
NSString *const kNetworkAddressKey = @"network address";
NSString *const kNetworkPortKey = @"network port";
NSString *const kNetworkUptimeSecondsKey = @"network uptime seconds";
NSString *const kNetworkBonjourKey = @"bonjour key";
That way the dictionary is very easy to visually parse when printed out:
{
"bonjour key" = Nerdinalia;
"network address" = "192.168.254.13";
"network down timestamp" = "2012-07-16 19:52:19 +0000";
"network interface" = en1;
"network port" = 666;
"network uptime seconds" = 10;
}
The other is to have the names match the constant you’d type in to access the dictionary:
NSString *const kNetworkWentByeByeNotification = @"kNetworkWentByeByeNotification";
NSString *const kNetworkInterfaceKey = @"kNetworkInterfaceKey";
NSString *const kNetworkDownTimestampKey = @"kNetworkDownTimestampKey";
NSString *const kNetworkAddressKey = @"kNetworkAddressKey";
NSString *const kNetworkPortKey = @"kNetworkPortKey";
NSString *const kNetworkUptimeSecondsKey = @"kNetworkUptimeSecondsKey";
NSString *const kNetworkBonjourKey = @"kNetworkBonjourKey";
The advantage of this method is for programmers that aren’t very familiar with your API. They can figure out which dictionary keys to access to get at the data they want. This is the same userInfo
dict, but with the other nomenclature:
{
kNetworkBonjourKey = Nerdinalia;
kNetworkAddressKey = "192.168.254.13";
kNetworkDownTimestampKey = "2012-07-16 19:52:49 +0000";
kNetworkInterfaceKey = en1;
kNetworkPortKey = 666;
kNetworkUptimeSecondsKey = 10;
}
It’s obvious that if you want the network interface, you would use kNetworkInterfaceKey to get at it.
It’s pretty easy to spy on notifications flying around your program. The notification key names are usually self-documenting (especially those coming from cocoa), as are the contents of the userInfo.
Recently I had a need to see notifications flying around my app. Specifically, I was wondering why I wasn’t getting NSMetadataQuery
notifications for my iCloud document container. I thought I had everything set up, and checked my cloud document marklar with NSFileManager
and there were files there, but nothing came back with my metadata query. I know there are some metadata query notifications happening, so I wanted to look at them.
It’s pretty easy to spy on all the notifications that are happening. Just register a notification handler with a nil object and a nil name. Passing nil for those tell the notification center to treat them like wild cards. Here’s a way to spy on everything going through your app’s notification center:
NSNotificationCenter *center;
center = [NSNotificationCenter defaultCenter];
token = [center addObserverForName: nil
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
QuietLog (@"NOTIFICATION %@ -> %@",
notification.name, notification.userInfo);
}];
The block API is really easy for stand-alone handlers like this. If you intend on removing this handler you’ll want to hang on to the returned token and pass it to removeObserver:
.
This is pretty cool. Here’s some of the stuff it prints on launch:
NOTIFICATION NSWillBecomeMultiThreadedNotification -> (null)
NOTIFICATION _UIWindowDidCreateWindowContextNotification -> {
"_UIWindowContextIDKey" = "-464166441";
}
...
NOTIFICATION UIWindowDidBecomeVisibleNotification -> (null)
NOTIFICATION UIDeviceOrientationDidChangeNotification -> {
UIDeviceOrientationRotateAnimatedUserInfoKey = 1;
}
NOTIFICATION UIWindowDidBecomeKeyNotification -> (null)
NOTIFICATION UIApplicationDidFinishLaunchingNotification -> (null)
NOTIFICATION UIApplicationDidBecomeActiveNotification -> (null)
NOTIFICATION _UIApplicationDidEndIgnoringInteractionEventsNotification -> (null)
...
NOTIFICATION UINavigationControllerDidShowViewControllerNotification -> {
UINavigationControllerLastVisibleViewController =
"<GRMainMenuViewController: 0x255320>";
UINavigationControllerNextVisibleViewController =
"<GRClassChooserViewController: 0x2e0d80>";
This ended up being useful to me because I saw no metadata notifications. Then I noticed I never called -startQuery
. I make dumb errors too.
We usually use the default notification center. You’re welcome to create your own notification centers if you wish (therefore NSNotificationCenter
is not a singleton class). On the desktop, you can access NSDistributedNotificationCenter
, which is a easy form of interprocess communication. One process can post a distributed notification, and other processes can register handlers to receive those notifications. To see everything that comes across distributed notifications, you just add an observer with nil name and object like with the default notification center:
center = [NSDistributedNotificationCenter defaultCenter];
token = [center addObserverForName: nil
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
QuietLog (@"DISTRIBUTED %@ -> %@",
notification.name, notification.userInfo);
}];
You can see distributed notifications when tracking menus twiddling stuff in the system preferences:
DISTRIBUTED com.apple.HIToolbox.beginMenuTrackingNotification -> {
ToolboxMessageEventData = 145;
}
DISTRIBUTED com.apple.HIToolbox.endMenuTrackingNotification -> (null)
DISTRIBUTED AppleShowScrollBarsSettingChanged -> (null)
DISTRIBUTED com.apple.HIToolbox.beginMenuTrackingNotification -> {
ToolboxMessageEventData = 25921;
}
DISTRIBUTED com.apple.HIToolbox.endMenuTrackingNotification -> (null)
DISTRIBUTED AppleSideBarDefaultIconSizeChanged -> (null)
Finally, there’s NSWorkspace
, which has its own notification center. NSWorkspace
will tip you off to cool events like application launches and exits, applications juggling in and out, and the machine going to sleep. Registering a notification is the same as before, just using a different notification center:
center = [[NSWorkspace sharedWorkspace] notificationCenter];
token = [center addObserverForName: nil
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
QuietLog (@"WORKSPACE %@ -> %@",
notification.name, notification.userInfo);
}];
And then spy on some of the workspace notifications:
WORKSPACE NSWorkspaceDidActivateApplicationNotification -> {
NSWorkspaceApplicationKey =
"<NSRunningApplication: 0x7ff56aa01230 (com.literatureandlatte.scrivener2 - 25588)>";
}
WORKSPACE NSWorkspaceDidDeactivateApplicationNotification -> {
NSWorkspaceApplicationKey =
"<NSRunningApplication: 0x7ff568d09970 (com.apple.Terminal - 145)>";
}
WORKSPACE NSWorkspaceWillSleepNotification -> (null)
WORKSPACE NSWorkspaceDidWakeNotification -> (null)
WORKSPACE NSWorkspaceDidTerminateApplicationNotification -> {
NSApplicationName = "backupd-helper";
NSApplicationPath =
"/System/Library/CoreServices/backupd.bundle/Contents/Resources/backupd-helper";
NSApplicationProcessIdentifier = 25947;
NSApplicationProcessSerialNumberHigh = 0;
NSApplicationProcessSerialNumberLow = 3703688;
NSWorkspaceApplicationKey =
"<NSRunningApplication: 0x7ff568e12da0 ((null) - 25947)>";
NSWorkspaceExitStatusKey = 0;
}
You can see I juggled from Scrivener (where I’m writing this posting) to the Terminal (where I’m working on the code and running it), the system going to sleep and waking up, and then the backup helper exiting.
If you’re having trouble seeing workspace notifications, you might need to do the hack I outlined earlier. You can get the code to spy on notifications from a command-line tool at this gist, and you’re welcome to paste it into your own applications to spy on the notifications flying around.
(Tune in Thursday for the final part, gotchas.)
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...