When it comes to iOS we are spoilt for choice. There are no less than three APIs available for service discovery.
Starting at the Foundation level we have the Objective-C class NSNetServiceBrowser
.
1.0 Instance Creation
The NSNetServiceBrowser
class defines a single no argument init
method so to create one we simply do this
browser = [[NSNetServiceBrowser alloc] init];
2.0 Starting A Search
The search for services of a given type is asynchronous.
To start the search we call the method
- (void)searchForServicesOfType:(NSString*)type inDomain:(NSString*)domainString;
The type
argument should be the domain relative name of the service type to search for, e.g.,
"_ipp._tcp."
Note the dot (‘.’) at the end.
The domainString
should be the absolute name of the domain to search, e.g.,
"local."
Note the dot (‘.’) at the end.
Once the search has started it will continue indefinitely unless it is explicitly stopped.
3.0 NSNetServiceBrowserDelegate
NSNetServiceBrowser
uses the standard delegate pattern to return its results.
The delegate is required to implement the NSNetServiceBrowserDelegate
protocol.
3.1 netServiceBrowserWillSearch:
Following the call to the method searchForServicesOfType:inDomain:
if the NSNetServiceBrowser
instance is going to perform the search it calls the delegate’s implementation of the method
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser*)netServiceBrowser
3.2 netServiceBrowser:didNotSearch:
Following the call to the method searchForServicesOfType:inDomain:
if the NSNetServiceBrowser
instance is not going to perform the search it calls the delegate’s implementation of the method
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser didNotSearch:(NSDictionary*)errorInfo
The easiest way to see this method in action is to get the type name wrong in the call to searchForServicesOfType:inDomain:
, e.g.,
"._ipp.tcp."
in which case the NSDictionary
passed as the errorInfo
argument will look like this
{
NSNetServicesErrorCode = "-72004";
NSNetServicesErrorDomain = 10;
}
You can find the error codes in NSNetServices.h
.
3.3 netServiceBrowser:didFindService:moreComing:
Following the call to the method searchForServicesOfType:inDomain:
if the NSNetServiceBrowser
instance finds a service of the given type it calls the delegate’s implementation of the method
- (void)netServiceBrowser:
(NSNetServiceBrowser*)netServiceBrowser
didFindService:
(NSNetService*)netService
moreComing:
(BOOL)moreServicesComing
3.4 netServiceBrowser:didRemoveService:moreComing:
If a service that has previously been found is no longer available the NSNetServiceBrowser
instance calls the delegate’s implementation of the method
- (void)netServiceBrowser:
(NSNetServiceBrowser*)netServiceBrowser
didRemoveService:
(NSNetService*)netService
moreComing:
(BOOL)moreServicesComing
4.0 NSNetService
As discovered, the service as represented by the NSNetService
instance is still in a nascent state. Neither its address nor the key/value pairs in its TXT record are available.
To be useful it has to be resolved.
The resolution of a service is performed asynchronously. It is started by invoking the NSNetService
method resolveWithTimeout:
which is declared like this
- (void)resolveWithTimeout:(NSTimeInterval)timeout
Once started the resolution of a service will either complete successfully ot time out after the interval specified by the timeout
argument.
5.0 NSNetServiceDelegate
The success or failure of the resolution of a NSNetService instance is reported to the delegate of that instance.
The delegate must implement the NSNetServiceDelegate
protocol.
5.1 netServiceWillResolve:
Following the call to the resolveWithTimeout:
method the NSNetService
instance will call its delegate’s implementation of the method
- (void)netServiceWillResolve:(NSNetService*)sender
5.2 netService:didNotResolve:
If the resolution of the service fails the NSNetService
instance calls its delegate’s implementation of the method
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
5.3 netServiceDidResolveAddress:
If the resolution of the service succeeds the NSNetService
instance calls its delegate’s implementation of the method
- (void)netServiceDidResolveAddress:(NSNetService*)sender;t
There is a caveat however. As we shall see ‘success’ in this context does not always mean what you might expect.
6.0 The FindServices Class
To make things slightly simpler we can wrap up the classes and their delegates in a single class FindServices
as we did in the Java case.
FindServicesDelegate.h
//
// FindServicesDelegate.h
// XperTakeOne
//
// Created by Simon Lewis on 03/11/2013.
// Copyright (c) 2013 Simon Lewis. All rights reserved.
//
#import <Foundation/Foundation.h>
@class FindServices;
@class Service;
@protocol FindServicesDelegate <NSObject>
- (void)findServices:(FindServices*)theFindServices didFindService:(Service*)theService;
- (void)findServices:(FindServices*)theFindServices didLoseService:(Service*)theService;
@end
FindServices.h
//
// FindServices.h
// XperTakeOne
//
// Created by Simon Lewis on 03/11/2013.
// Copyright (c) 2013 Simon Lewis. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FindServicesDelegate.h"
@interface FindServices : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate>
@property (weak) id<FindServicesDelegate> delegate;
- (FindServices*)initWithType:(NSString*)theType andDomain:(NSString*)theDomain;
- (void)start;
@end
FindServices.m
//
// FindServices.m
// XperTakeOne
//
// Created by Simon Lewis on 03/11/2013.
// Copyright (c) 2013 Simon Lewis. All rights reserved.
//
#import "Service.h"
#import "FindServices.h"
@interface FindServices ()
@property NSString* type;
@property NSString* domain;
@property NSNetServiceBrowser* browser;
@property NSMutableArray* resolving;
@property NSMutableDictionary* services;
@end
@implementation FindServices
- (FindServices*)initWithType:(NSString*)theType andDomain:(NSString*)theDomain
{
self = [super init];
if (self != nil)
{
self.type = theType;
self.domain = theDomain;
self.browser = [[NSNetServiceBrowser alloc] init];
self.resolving = [NSMutableArray arrayWithCapacity:8];
self.services = [NSMutableDictionary dictionaryWithCapacity:8];
self.browser.delegate = self;
}
return self;
}
- (void)start
{
[self.browser searchForServicesOfType:self.type inDomain:self.domain];
}
// NSNetServiceBrowserDelegate
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)theBrowser
{
NSLog(@"netServiceBrowserWillSearch:\n");
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)theBrowser didNotSearch:(NSDictionary *)theErrors
{
NSLog(@"netServiceBrowser:didNotSearch: %@", theErrors);
}
- (void)netServiceBrowser:
(NSNetServiceBrowser *)aNetServiceBrowser
didFindService:
(NSNetService *)theService
moreComing:
(BOOL)moreComing
{
NSLog(@"netServiceBrowser:didFindService: %@", theService);
[self.resolving addObject:theService];
theService.delegate = self;
[theService resolveWithTimeout:0.0];
}
- (void)netServiceBrowser:
(NSNetServiceBrowser *)aNetServiceBrowser
didRemoveService:
(NSNetService *)theService
moreComing:
(BOOL)moreComing
{
NSLog(@"netServiceBrowser:didRemoveService: %@", theService);
Service* service = [self.services objectForKey:theService.name];
if (service != nil)
{
[self.services removeObjectForKey:theService.name];
[self.delegate findServices:self didLoseService:service];
}
else
{
NSLog(@"%@ removed without being found ?", theService.name);
}
}
// NSNetServiceDelegate
- (void)netServiceWillResolve:(NSNetService *)theService
{
NSLog(@"netServiceWillResolve");
}
- (void)netServiceDidResolveAddress:(NSNetService *)theService
{
NSUInteger nAddresses = [[theService addresses] count];
NSLog(@"netServiceDidResolveAddress: %@ nAddresses == %lu", theService, (unsigned long)nAddresses);
if (nAddresses != 0)
{
Service* service = [[Service alloc] init:theService];
[self.resolving removeObject:theService];
[self.services setObject:service forKey:theService.name];
[self.delegate findServices:self didFindService:service];
}
else
{
Service* service = [self.services objectForKey:theService.name];
if (service != nil)
{
NSLog(@"service %@ now has 0 addresses !", theService.name);
}
else
{
NSLog(@"resolve failed ? %@ has 0 addresses", theService.name);
}
}
}
- (void)netService:(NSNetService *)theService didNotResolve:(NSDictionary *)theErrors
{
NSLog(@"netServiced:didNotResolve: %@ %@", theService, theErrors);
[self.resolving removeObject:theService];
}
@end
7.0 Examples
In each case FindServices
is looking for services of type
"_ipp._tcp."
in the domain
"local."
In each case the log output is from FindServices
and its delegate running on an iPad running iOS 7.0.
7.1 A Single IPPServer
A single instance of the CUPS test server IPPServer
with the name ipp_server_1
running on a Mac and then being stopped.
...
2013-11-12 08:26:05.516 XperTakeOne[163:60b] netServiceBrowserWillSearch:
2013-11-12 08:26:12.500 XperTakeOne[163:60b] netServiceBrowser:didFindService: \
<NSNetService 0x1464bd60> local. _ipp._tcp. ipp_server_1
2013-11-12 08:26:12.504 XperTakeOne[163:60b] netServiceWillResolve
2013-11-12 08:26:12.533 XperTakeOne[163:60b] netServiceDidResolveAddress: \
<NSNetService 0x1464bd60> local. _ipp._tcp. ipp_server_1 nAddresses == 2
2013-11-12 08:26:12.535 XperTakeOne[163:60b] findServices:didFindService: <Service: 0x14631890>
2013-11-12 08:27:56.204 XperTakeOne[163:60b] netServiceBrowser:didRemoveService: \
<NSNetService 0x14522f50> local. _ipp._tcp. ipp_server_1
2013-11-12 08:27:56.207 XperTakeOne[163:60b] findServices:didLoseService: <Service: 0x14631890>
...
7.2 Two IPPServers
Two instances of the CUPS test server IPPServer
with the names ipp_server_1
and ipp_server_2
running on a Mac and then being stopped.
...
2013-11-12 08:29:54.404 XperTakeOne[171:60b] netServiceBrowserWillSearch:
2013-11-12 08:29:54.525 XperTakeOne[171:60b] netServiceBrowser:didFindService: \
<NSNetService 0x1757ac10> local. _ipp._tcp. ipp_server_1
2013-11-12 08:29:54.528 XperTakeOne[171:60b] netServiceWillResolve
2013-11-12 08:29:54.531 XperTakeOne[171:60b] netServiceBrowser:didFindService: \
<NSNetService 0x17578140> local. _ipp._tcp. ipp_server_2
2013-11-12 08:29:54.533 XperTakeOne[171:60b] netServiceWillResolve
2013-11-12 08:29:55.553 XperTakeOne[171:60b] netServiceDidResolveAddress:\
<NSNetService 0x17578140> local. _ipp._tcp. ipp_server_2 nAddresses == 2
2013-11-12 08:29:55.556 XperTakeOne[171:60b] findServices:didFindService: \
<Service: 0x17673570>
2013-11-12 08:29:55.558 XperTakeOne[171:60b] netServiceDidResolveAddress: \
<NSNetService 0x1757ac10> local. _ipp._tcp. ipp_server_1 nAddresses == 2
2013-11-12 08:29:55.559 XperTakeOne[171:60b] findServices:didFindService: <Service: 0x176b25f0>
2013-11-12 08:30:57.454 XperTakeOne[171:60b] netServiceBrowser:didRemoveService: \
<NSNetService 0x1757e7d0> local. _ipp._tcp. ipp_server_2
2013-11-12 08:30:57.457 XperTakeOne[171:60b] findServices:didLoseService: <Service: 0x17673570>
2013-11-12 08:31:02.678 XperTakeOne[171:60b] netServiceBrowser:didRemoveService: \
<NSNetService 0x17581730> local. _ipp._tcp. ipp_server_1
2013-11-12 08:31:02.680 XperTakeOne[171:60b] findServices:didLoseService: <Service: 0x176b25f0>
...
7.3 A Single Printer
A printer being turned on and then turned off five minutes later.
Note the lines shown in bold for emphasis.
When the printer is turned off the netServiceDidResolveAddress:
method is being called for a second time but this time the NSNetService
instance has no addresses.
This is the reason for the convoluted code in the FindServices
implementation of the netServiceDidResolveAddress:
.
Note also that this does not happen when an IPPServer instance is shutdown.
...
2013-11-12 08:32:14.253 XperTakeOne[179:60b] netServiceBrowserWillSearch:
2013-11-12 08:32:14.976 XperTakeOne[179:60b] netServiceBrowser:didFindService: \
<NSNetService 0x146b3a50> local. _ipp._tcp. Canon MG6200 series
2013-11-12 08:32:14.979 XperTakeOne[179:60b] netServiceWillResolve
2013-11-12 08:32:15.008 XperTakeOne[179:60b] netServiceDidResolveAddress: \
<NSNetService 0x146b3a50> local. _ipp._tcp. Canon MG6200 series nAddresses == 1
2013-11-12 08:32:15.011 XperTakeOne[179:60b] findServices:didFindService: <Service: 0x14681460>
2013-11-12 08:37:10.250 XperTakeOne[179:60b] netServiceDidResolveAddress: \
<NSNetService 0x146b3a50> local. _ipp._tcp. Canon MG6200 series nAddresses == 0
2013-11-12 08:37:10.252 XperTakeOne[179:60b] service Canon MG6200 series now has 0 addresses !
2013-11-12 08:37:11.325 XperTakeOne[179:60b] netServiceBrowser:didRemoveService: \
<NSNetService 0x146b21d0> local. _ipp._tcp. Canon MG6200 series
2013-11-12 08:37:11.327 XperTakeOne[179:60b] findServices:didLoseService: <Service: 0x14681460>
...
Copyright (c) 2013 By Simon Lewis. All Rights Reserved.
Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.
Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.