Suppose you are working on a program to read USENET news, and have an object with a method to encrypt and decrypt articles, such as the one in Listing 1. News articles containing offensive material are often encrypted with this algorithm, called “rot13,” in which letters are shifted halfway through the alphabet.
Listing 1 Text encryption method
- (NSString *)rotateLettersInString:(NSString *)aString |
{ |
NSString *newString; |
unsigned length; |
unichar *buf; |
unsigned i; |
length = [aString length]; |
buf = malloc( (length + 1) * sizeof(unichar) ); |
[aString getCharacters:buf]; |
buf[length] = (unichar)0; // not really needed.... |
for (i = 0; i < length; i++) { |
if (buf[i] >= (unichar)'a' && buf[i] <= (unichar) 'z') { |
buf[i] += 13; |
if (buf[i] > 'z') buf[i] -= 26; |
} else if (buf[i] >= (unichar)'A' && buf[i] <= (unichar) 'Z') { |
buf[i] += 13; |
if (buf[i] > 'Z') buf[i] -= 26; |
} |
} |
newString = [NSString stringWithCharacters:buf length:length]; |
free(buf); |
return newString; |
} |
Implementing the Service Method
Registering the Service Provider
Advertising the Service
Installing the Service
Since this feature is generally useful as a simple encryption scheme, it can be exported to other applications. To offer this functionality as a service, write a method such as the one in Listing 2.
- (void)simpleEncrypt:(NSPasteboard *)pboard |
userData:(NSString *)userData |
error:(NSString **)error |
{ |
NSString *pboardString; |
NSString *newString; |
NSArray *types; |
types = [pboard types]; |
if (![types containsObject:NSStringPboardType]) { |
*error = NSLocalizedString(@"Error: couldn't encrypt text.", |
@"pboard couldn't give string."); |
return; |
} |
pboardString = [pboard stringForType:NSStringPboardType]; |
if (!pboardString) { |
*error = NSLocalizedString(@"Error: couldn't encrypt text.", |
@"pboard couldn't give string."); |
return; |
} |
newString = [self rotateLettersInString:pboardString]; |
if (!newString) { |
*error = NSLocalizedString(@"Error: couldn't encrypt text.", |
@"self couldn't rotate letters."); |
return; |
} |
types = [NSArray arrayWithObject:NSStringPboardType]; |
[pboard declareTypes:types owner:nil]; |
[pboard setString:newString forType:NSStringPboardType]; |
return; |
} |
The method providing the service is of the form messageName:userData:error:
and takes arguments as shown in Listing 2. The method itself takes data from the pasteboard as needed, operates on it, and writes any results back to the pasteboard. In case of an error, the method simply sets the pointer given by the error argument to a non-nil NSString and returns. The error message is logged to the console. The userData argument is not used here.
If implementing the service in Java, the invoked method has the form
String messageName(NSPasteboard pboard, String userData) |
This method returns null
if successful; otherwise, it returns the error message string.
Now you have an object with methods that allow it to perform a service for another application. Next, you need to register the object at run time so the services facility knows which object to have perform the service. You create and register your object in the applicationDidFinishLaunching:
application delegate method (or equivalent) with NSApplication’s setServicesProvider:
method. If your object is called encryptor
you create and register it with this code fragment:
EncryptoClass *encryptor; |
encryptor = [[EncryptoClass alloc] init]; |
[NSApp setServicesProvider:encryptor]; |
If you are writing a plain Foundation tool, which lacks an NSApplication object, register the service object with the NSRegisterServicesProvider
function. Its declaration is:
void NSRegisterServicesProvider(id provider, NSString *portName) |
provider is the object that provides the services, and portName is the same value you specify for the NSPortName
entry in the services specification. After making this function call, you must enter the run loop to respond to service requests.
You can register only one service provider per application. If you have more than one service to provide, a single object must provide the interface to all of the services.
Service requests can arrive immediately after registering the object, in some circumstances even before exiting applicationDidFinishLaunching:
. Therefore, register your service provider only when you are completely ready to process requests.
For the system to know that your application provides a service, you must advertise that fact. You do this by adding an entry to your application project’s Info.plist
file. The entry you add is called the service specification. In our example, the service specification looks like this:
NSServices = ( |
{ |
NSPortName = NewsReader; |
NSMessage = simpleEncrypt; |
NSSendTypes = (NSStringPboardType); |
NSReturnTypes = (NSStringPboardType); |
NSMenuItem = { |
default = "Encrypt Text"; |
}; |
NSKeyEquivalent = { |
default = E; |
}; |
} |
); |
A service can be offered as part of an application, such as Mail, or as a standalone service—one without a user interface that is intended for use only in the Services menu. Applications that offer services should be built with the .app
extension and installed in the Applications
folder (or a subfolder) in one of the four file-system domains—System, Network, Local, and User. (See “File-System Domains” in System Overview for details.) A standalone service should be built with the .service
extension and stored in the Library/Services
folder in one of these domains.
The list of available services on the computer is built each time a user logs in. After installing your service in either an Applications
or Library/Services
directory, you need to log out and back in again before the service becomes available. You can force an update of the list of services without logging out by calling the following function:
void NSUpdateDynamicServices(void) |
Running applications are not affected, but applications launched afterwards get the new list of services.
© 2003, 2002 Apple Computer, Inc. All Rights Reserved. (Last updated: 2002-11-12)