|
In my previous Leopard sample, I took a look at how to use the NSCollectionView to bind to a collection of items with a custom view rendered for each of the items. The main benefit of this control is that you get full animation capabilities, allowing new items added to the collection to fade in, to shuffle in, or whatever other kind of animation you want. Items removed from the collection also animate out.
In this update to the sample, I've decided that my monster compendium should be a collaborative application. As such, I want it to detect all nearby Dungeon Masters who are currently working on monster lists. To do this, I'm going to advertise the presence of my application on the local network with Bonjour. Note that with Leopard, I could advertise it globally as well. I noticed that when I used the empty string for my registration domain (instead of "local.", as you'll see in the code sample), my service actually registered with members.mac.com.
Here's a screenshot of the output of my application:
Couple of things to note. The "Nearby Dungeon Masters" list is bound to an array controller that contains a list of discovered services through Bonjour. Also, take a look at the Bonjour logo on the buttons. I didn't draw that, it's one of the any new icons that you can just pick from a dropdown list and have them appear anywhere in your application. This is fantastic because now you don't have to guess or approximate if you're trying to re-use something Apple has as a stock part of the OS.
There's two parts to being aware of other instances of your own application on a LAN. The first part is publishing the presence of your service (I'll consume the service through Distributed Objects in another code sample). The second part is browsing for, and discovering, other services.
To publish the existence of your own service, you can write some really simple code in a controller that looks like this:
- (IBAction)publishService:(id)sender
{
pubService = [[NSNetService alloc] initWithDomain:@"local." type:@"_moncom._tcp" name:[dmName stringValue] port:9000];
[pubService publish];
}
In the preceding sample, dmName is the text field that contains your own name (the Dungeon Master name). This name will be used as the name of the service when published so you can be quickly and easily identified. Other architectures often use some kind of unique ID as the service name, and you then have to connect to the service to obtain more information. Note the "_moncom._tcp" string. That's the service type. For more information on Bonjour (formerly Rendezvous) services, you can check out Apple's online documentation.
Now you're going to need to start browsing for services. Browsing is something that is a persistent activity. You create an instance of a browser and set the callback, and you will receive callbacks nearly immediately any time anything within your browse criteria takes place. You will be notified nearly immediately when new services of the type you're looking for appear, and when services of that type disappear. Here's the code I have to start my browser:
- (IBAction)startBrowsing:(id)sender
{
NSLog(@"Starting browse.");
browser = [[NSNetServiceBrowser alloc] init];
[browser setDelegate:self];
[browser searchForServicesOfType:@"_moncom._tcp." inDomain:@"local."];
}
This is pretty simple code. This is an action on a class file I have created called BonjourBrowser. This class ha the method to start browsing, and is the delegate for browsing activities. An interesting quirk that I noticed is that I want to start browsing immediately upon awaking from the NIB. This way, I will detect nearby DMs as soon as the application starts up. To do that, I have the following code in my awakeFromNib: method:
- (void)awakeFromNib
{
[bonjourBrowser performSelector:@selector(startBrowsing:) withObject:self afterDelay:0];
}
I was a little confused at first here. The issue is that if I just call startBrowsing: immediately during the NIB awakening, some aspect of Bonjour is not yet available to me, and I will be forever unable to detect services using that browser. However, if I use performSelector: to defer the call to the next event loop cycle, Bonjour has initialized itself and everybody's happy.
Finally, I want to show you the code that I have in the delegate for when a new service is detected. This code is responsible for adding the service into the array controller (which is the data-bound source for the "Nearby Dungeon Masters" NSTableView):
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser
didFindService:(NSNetService *)aNetService
moreComing:(BOOL)moreComing
{
NSLog(@"Added net service %@", [aNetService name]);
[discoveredServices addObject:aNetService];
}
In the next code sample, I'll show you how to take the list of nearby Dungeon Masters and actually send a monster from your desktop application (created using Core Data) to a remote Dungeon Master using Distributed Objects and resolving remote service addresses through Bonjour.
I'll also post the full source code for this sample after the next blog post.
I realize the screenshot completely sucks. I've made it so if you click the
picture, you'll see the picture in it's full size, which is around 900
pixels wide. That's what I get for coding on a 17" Macbook Pro
Hi Kevin,
I'm really looking forward to your next post. I haven't yet found any
working examples of distributed objects.
Wide-Area Bonjour is enabled, and yes, my .mac Bonjour registration was
part of that feature. I also agree that I believe there's some Bonjour
magic making "Back to my Mac" possible. I have yet to delve into either of
those very thoroughly, only just now exploring in any real detail the
capabilities of Bonjour for LAN-based peer discovery.