To manage this situation, we use a protocol. If you know Java, this is similar to an interface in Java. A protocol declares some methods, leaving the implementation unspecified. The language then allows you to declare that a certain object conforms to a certain protocol; this means that the object implements the methods listed in the protocol. In our example, this allows us to declare that we can send the getFile: method to the reader object, without actually knowing the implementation of the method nor actually knowing the class of the reader object.
The declaration of the protocol is as follows:
@protocol FileReader - (NSString *) getFile: (NSString *)fileName; @endThis declares the protocol FileReader to have a single method, getFile:. Objects conform to this protocol if and only if they have a getFile: method taking a NSString * argument, and returning an NSString *.
The reader object, which we used to declare to be of class FileReader,
FileReader *reader;is now declared more generically to conform to the FileReader protocol:
id <FileReader> reader;id means a generic object; <FileReader> means that it must conform to the FileReader protocol; in this case this simply means that reader is an object and you can send the message getFile: to it.
reader = [FileReader new];we now ask the gnustep-base library to give us the object registered with the name FileReader on a remote machine:
reader = (id <FileReader>)[NSConnection
rootProxyForConnectionWithRegisteredName: @"FileReader"
host: @"*"];
strictly speaking, reader is a local proxy to the remote
object - but the whole thing is made so that you can forget about
this distinction, and think of reader simply as the remote
object. Using * for the host argument means that
gnustep-base will look for an object registered with name
FileReader anywhere on the network; if you know the host on
which you want to access the FileReader object, you should
better use your specific host name, such as localhost or
192.14.29.1.
We need a cast to id <FileReader> because the call to NSConnection returns a generic object, while we know the FileReader object implements getFile:. A more robust application could check at execution time that the remote object in the server actually can respond to getFile: messages before doing the cast (for example by using the method respondsToSelector:); we skip this little complication in this first example.
But we need to check that we have a real reader object - if it is nil, it is because for some reason the gnustep-base library couldn't connect to an object registered as FileReader on the network. Usually this is because the server is not running; there is nothing we can do in the client in these cases, so we simply print an error message and exit.
As promised, the rest of the function is unchanged; in particular, when we send the getFile: method to the remote object, that starts a network connection to the server, and returns the result - but the nice thing is that we don't need to do anything special to perform this remote call: we just call the method normally, as if the object were our old friendly local object.
Here is the source code:
#include <Foundation/Foundation.h>
/* This tells us how the reader object behaves */
@protocol FileReader
- (NSString *)getFile: (NSString *)fileName;
@end
int
main (void)
{
NSAutoreleasePool *pool;
NSArray *args;
int count;
id <FileReader> reader;
NSString *filename;
NSString *file;
pool = [NSAutoreleasePool new];
/* Create our FileReader object */
reader = (id <FileReader>)[NSConnection
rootProxyForConnectionWithRegisteredName:
@"FileReader"
host: @"*"];
if (reader == nil)
{
NSLog (@"Error: could not connect to server");
exit (1);
}
/* From now on the code is the same, whether reader is
in the local process or in a remote one */
/* Get program arguments */
args = [[NSProcessInfo processInfo] arguments];
/* the first string in args is the program name;
get the second one if any */
if ([args count] == 1)
{
NSLog (@"Error: you should specify a filename");
exit (1);
}
filename = [args objectAtIndex: 1];
/* Ask the reader object to get the file */
file = [reader getFile: filename];
/* If the reader object could get the file, show it */
if (file != nil)
{
printf ("%s\n", [file lossyCString]);
}
else
{
NSLog (@"Error: could not read file `%@'", filename);
exit (1);
}
return 0;
}