iOS Reference Library Apple Developer
Search

Resolving and Using Network Services

This article describes how to use the NSNetService class to resolve discovered network services into socket information you can use to connect to a service.

The Resolution Process

The NSNetService class provides methods for resolving discovered services into network addresses and port numbers you can use to connect to a service. Resolution takes place every time a service is used because, although the service name is a persistent property, socket information (IP address and port number) can change from session to session. If a user browses for a service and saves that service, such as in a printer chooser, only the service name, type, and domain are stored. When it is time to connect, these values are resolved into socket information.

Because resolution can take time, especially if the service is unavailable, NSNetService resolves asynchronously, providing information to your application through a delegate object.

Resolving and using an NSNetService instance takes four steps:

  1. Obtain a NSNetService instance through initialization or service discovery.

  2. Resolve the service.

  3. Respond to messages sent to the object’s delegate about addresses or errors.

  4. Use the resulting addresses to connect to the service.

The following sections describe these steps in detail.

Obtaining and Resolving an NSNetService Object

You can obtain an NSNetService object representing the service you want to connect to in one of two ways:

See “Browsing for Network Services” for information about service browsing.

To create an NSNetService object for resolution rather than publication, use the initWithDomain:type:name: method.

Note: When browsing or resolving services, passing the empty string (@"") as the domain will specify the default list of domains, which includes local. and Back To My Mac. To limit your scope to a single domain or to access a domain not included in the default list, specify it explicitly, such as @"local.".

Once you have an NSNetService object to resolve, assign it a delegate and use the resolveWithTimeout: method to asynchronously search for socket addresses. When resolution is complete, the delegate receives a netServiceDidResolveAddress: or netService:didNotResolve: message if an error occurred. Because the delegate receives the identity of the NSNetService object as part of the delegate method, one delegate can serve multiple NSNetService objects.

Listing 1 demonstrates how to initialize and resolve an NSNetService object for a hypothetical music-sharing service. The code initializes the object with name serviceName, type _music._tcp, and the link-local suffix local.. It then assigns it a delegate and asks it to resolve the name into socket addresses.

Listing 1  Resolving network services with NSNetService

id delegateObject;      // Assume this exists.
NSString *serviceName;  // Assume this exists.
NSNetService *service;
 
service = [[NSNetService alloc] initWithDomain:@"local." type:@"_music._tcp"
                                name:serviceName];
[service setDelegate:delegateObject];
[service resolveWithTimeout:5.0];

Implementing Delegate Methods for Resolution

NSNetService returns resolution results to its delegate. If you are resolving a service, your delegate object should implement the following methods:

The netServiceDidResolveAddress: method tells the delegate that the NSNetService object has added an address to its list of addresses for the service. However, more addresses may be added. For example, in systems that support both IPv4 and IPv6, netServiceDidResolveAddress: may be called two or more times: once for the IPv4 address and again for the IPv6 address. You should analyze the result of the addresses call before attempting to use the service to ensure that all the required connection information is present. If there’s information missing, netServiceDidResolveAddress: will be called later. If multiple addresses are returned from resolving a service, try to connect to each one before giving up.

If resolution fails for any reason, the netService:didNotResolve: method is called. If the delegate receives a netService:didNotResolve: message, you should extract the type of error from the returned dictionary using the NSNetServicesErrorCode key and handle the error accordingly. See NSNetService for a list of possible errors.

Listing 2 shows the interface for a class that acts as a delegate for multiple NSNetService objects, and Listing 3 shows its implementation. You can use this code as a starting point for more sophisticated tracking of resolved services.

Listing 2  Interface for an NSNetService delegate object (resolution)

#import <Foundation/Foundation.h>
 
@interface NetServiceResolutionDelegate : NSObject
{
    // Keeps track of services handled by this delegate
    NSMutableArray *services;
}
 
// NSNetService delegate methods for publication
- (void)netServiceDidResolveAddress:(NSNetService *)netService;
- (void)netService:(NSNetService *)netService
        didNotResolve:(NSDictionary *)errorDict;
 
// Other methods
- (BOOL)addressesComplete:(NSArray *)addresses
        forServiceType:(NSString *)serviceType;
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service;
 
@end

Listing 3  Implementation for an NSNetService delegate object (resolution)

#import "NetServiceResolutionDelegate.h"
 
@implementation NetServiceResolutionDelegate
 
- (id)init
{
    self = [super init];
    if (self) {
        services = [[NSMutableArray alloc] init];
    }
    return self;
}
 
- (void)dealloc
{
    [services release];
    [super dealloc];
}
 
// Sent when addresses are resolved
- (void)netServiceDidResolveAddress:(NSNetService *)netService
{
    // Make sure [netService addresses] contains the
    // necessary connection information
    if ([self addressesComplete:[netService addresses]
            forServiceType:[netService type]]) {
        [services addObject:netService];
    }
}
 
// Sent if resolution fails
- (void)netService:(NSNetService *)netService
        didNotResolve:(NSDictionary *)errorDict
{
    [self handleError:[errorDict objectForKey:NSNetServicesErrorCode]];
    [services removeObject:netService];
}
 
// Verifies [netService addresses]
- (BOOL)addressesComplete:(NSArray *)addresses
        forServiceType:(NSString *)serviceType
{
    // Perform appropriate logic to ensure that [netService addresses]
    // contains the appropriate information to connect to the service
    return YES;
}
 
// Error handling code
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service
{
    NSLog(@"An error occurred with service %@.%@.%@, error code = %@",
        [service name], [service type], [service domain], error);
    // Handle error here
}
 
@end

Connecting to a Network Service

Once an NSNetService object’s delegate is notified that addresses have been resolved for the service, your application can connect. The recommended way to do this is with the getInputStream:outputStream: method of the NSNetService class. This method provides a reference to an input stream (NSInputStream) and an output stream (NSOutputStream), both of which you may access synchronously or asynchronously. To interact asynchronously, you need to schedule the streams in the current run loop and assign them a delegate object. These streams provide a protocol-independent means of communicating with network services.

Listing 4 demonstrates how to connect to an NSNetService using streams. Note that if you require only one of the two streams, it is not necessary for you to do anything with the other. See the PictureSharingBrowser application in /Developer/Examples/Foundation/PictureSharingBrowser for a complete example of service resolution with NSStream objects.

Listing 4  Connecting to a resolved Bonjour network service

#import <sys/socket.h>
#import <netinet/in.h>
 
// ...
 
NSNetService *service;  // Assume this exists. For instance, you may
                        // have received it from an NSNetServiceBrowser
                        // delegate callback.
 
NSInputStream *istream = nil;
NSOutputStream *ostream = nil;
 
[service getInputStream:&istream outputStream:&ostream];
if (istream && ostream)
{
    // Use the streams as you like for reading and writing.
}
else
{
    NSLog(@"Failed to acquire valid streams");
}

Monitoring a Service

If you want to monitor a service for changes, Mac OS X v10.4 has two new methods to provide this functionality. Monitoring a service could be useful in a situation such as a chat program. If a user (Matt) were to change his status from available to idle, the other users should learn this new information quickly, without having to constantly poll Matt's machine looking for his status. Enter startMonitoring and stopMonitoring.

By calling startMonitoring on your service Bonjour will monitor your service and call a delegate function if anything changes. The delegate method is named netService:didUpdateTXTRecordData: and passes the new TXT record as the didUpdateTXTRecordData parameter. When you no longer need to monitor the TXT record call stopMonitoring on your service.




Last updated: 2010-03-24

Did this document help you? Yes It's good, but... Not helpful...