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; }