There’s nothing intrinsically specific about the
roles and the platforms chosen. A Mac could just as easily provide a
service and an iPhone its client. Since an iPhone/iPhone client/server
pair is best implemented with GameKit, this recipe demonstrates how to
cross platforms and use Bonjour for mobile desktop communication.
Recipe 1
limits itself to the methods specific to the Bonjour communications.
A Bonjour client begins by browsing for services.
Since the service on the iPhone is provided by Bonjour and not GameKit,
its name is known in advance of compilation and testing, namely @"PictureThrow".
The NSNetServiceBrowser class provides the ability to find a given service type. Its delegate receives a netServiceBrowser:didFindService:moreComing: callback when a match appears. The delegate can then stop the browser and begin to resolve the service.
Connections can close for three reasons. First,
the data transferred over successfully and the host service closed it
deliberately. Second, the user may have denied the connection. Third,
the application might have lost its connection by quitting or moving out
of range. The same connectionDidClose: callback must handle all three cases.
In this recipe, this callback sets a Boolean value for success. When the connection closes, the connectionDidClose:
callback method checks that value. If the data transfer did not
succeed, the user is told that the connection was denied or lost.
Recipe 1. Providing a Bonjour Client
// Receive the image and update the interface
- (void) connection:(TCPConnection*)connection
didReceiveData:(NSData*)data;
{
// Upon receiving the image, update the image view
success = YES;
self.imageData = data;
NSImage *image = [self imageFromData:data];
[imageView setImage:image];
// You can now save or catch another image
[saveItem setEnabled:YES];
[button setEnabled:YES];
[progress stopAnimation:nil];
// Update status
ANNOUNCE(@"Recived JPEG image (%d bytes).\n\nUse File > Save\
to save the received image to disk.", data.length);
}
// If there was no success, apologize and restore UI
- (void) connectionDidClose:(TCPConnection*)connection
{
// Failed or completed connection. Check for success.
if (success) return;
ANNOUNCE(@"Connection denied or lost. Sorry.");
// For a failed connection, prepare for the next catch
self.imageData = nil;
[saveItem setEnabled:NO];
[imageView setImage:nil];
[button setEnabled:YES];
[progress stopAnimation:nil];
}
// Upon resolving address, create a connection to that address
// and request data
- (void)netServiceDidResolveAddress:(NSNetService *)netService
{
// Gather the addresses and attempt to create a connection
NSArray* addresses = [netService addresses];
if (addresses && [addresses count]) {
struct sockaddr* address = (struct sockaddr*)[[addresses
objectAtIndex:0] bytes];
TCPConnection *connection = [[TCPConnection alloc]
initWithRemoteAddress:address];
[connection setDelegate:self];
[statusText setTitleWithMnemonic:@"Requesting data..."];
[progress startAnimation:nil];
[netService release];
[connection receiveData];
}
}
// Complain when resolve fails
- (void)netService:(NSNetService *)sender didNotResolve:
(NSDictionary *)errorDict {
[statusText setTitleWithMnemonic:
@"Error resolving service. Sorry."];
}
// Upon finding a service, stop the browser and resolve
- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
didFindService:(NSNetService *)netService
moreComing:(BOOL)moreServicesComing
{
[self.browser stop];
self.browser = nil;
[statusText setTitleWithMnemonic:@"Resolving service."];
[[netService retain] setDelegate:self];
[netService resolveWithTimeout:0.0f];
}
// Begin a catch request, start the service browser, and update UI
- (IBAction) catchPlease: (id) sender
{
success = NO;
[self.statusText setTitleWithMnemonic:@"Scanning for service"];
// Create a new service browser
self.browser = [[[NSNetServiceBrowser alloc] init] autorelease];
[self.browser setDelegate:self];
NSString *type = [TCPConnection
bonjourTypeFromIdentifier:@"PictureThrow"];
[self.browser searchForServicesOfType:type inDomain:@"local"];
// Disable and reset the interactive features while waiting
[button setEnabled:NO];
self.imageData = nil;
[saveItem setEnabled:NO];
[imageView setImage:nil];
}
|