Example of a File System Client in Objective-C
This page presents a very simple client to access a server that implements the file system we developed in Slice for a Simple File System. The Objective-C code shown here hardly differs from the code you would write for an ordinary Objective-C program. This is one of the biggest advantages of using Ice: accessing a remote object is as easy as accessing an ordinary, local Objective-C object. This allows you to put your effort where you should, namely, into developing your application logic instead of having to struggle with arcane networking APIs. This is true for the server side as well, meaning that you can develop distributed applications easily and efficiently.
We now have seen enough of the client-side Objective-C mapping to develop a complete client to access our remote file system. For reference, here is the Slice definition once more:
["objc:prefix:FS"] module Filesystem { exception GenericError { string reason; } interface Node { idempotent string name(); } sequence<string> Lines; interface File extends Node { idempotent Lines read(); idempotent void write(Lines text) throws GenericError; } sequence<Node*> NodeSeq; interface Directory extends Node { idempotent NodeSeq list(); } }
To exercise the file system, the client does a recursive listing of the file system, starting at the root directory. For each node in the file system, the client shows the name of the node and whether that node is a file or directory. If the node is a file, the client retrieves the contents of the file and prints them.
The body of the client code looks as follows:
#import <objc/Ice.h> #import <Filesystem.h> #import <stdio.h> static void listRecursive(id<FSDirectoryPrx> dir, int depth) { // ... } int main(int argc, char* argv[]) { int status = EXIT_FAILURE; @autoreleasepool { id<ICECommunicator> communicator = nil; @try { communicator = [ICEUtil createCommunicator:&argc argv:argv]; // // Create a proxy for the root directory // id<FSDirectoryPrx> rootDir = [FSDirectoryPrx checkedCast: [communicator stringToProxy:@"RootDir:default -p 10000"]]; if(!rootDir) { [NSException raise:@"invalid proxy" format:@"nil"]; } // // Recursively list the contents of the root directory // printf("Contents of root directory:\n"); listRecursive(rootDir, 0); status = EXIT_SUCCESS; } @catch (NSException *ex) { NSLog(@"%@\n", ex); } [communicator destroy]; } return status; }
- The code imports a few header files:
obj/Ice.h
: Always included in both client and server source files, provides definitions that are necessary for accessing the Ice run time.Filesystem.h
The header that is generated by the Slice compiler from the Slice definitions inFilesystem.ice
.stdio.h
: The implementation oflistRecursive
prints tostdout
.
- The structure of the code in
main
follows what we saw in Hello World Application. After initializing the run time, the client creates a proxy to the root directory of the file system. For this example, we assume that the server runs on the local host and listens using the default transport protocol (TCP/IP) at port 10000. The object identity of the root directory is known to beRootDir
. - The client down-casts the proxy to
DirectoryPrx
and passes that proxy tolistRecursive
, which prints the contents of the file system.
Most of the work happens in listRecursive
:
// Print the specified number of tabs. static void printIndent(int depth) { while(depth-- > 0) { putchar('\t'); } } // Recursively print the contents of directory "dir" in tree fashion. // For files, show the contents of each file. The "depth" // parameter is the current nesting level (for indentation). static void listRecursive(id<FSDirectoryPrx> directory, int depth) { ++depth; FSNodeSeq *contents = [directory list]; for(id<FSNodePrx> node in contents) { id<FSDirectoryPrx> dir = [FSDirectoryPrx checkedCast:node]; id<FSFilePrx> file = [FSFilePrx uncheckedCast:node]; printIndent(depth); printf("%s%s\n", [[node name] UTF8String], (dir ? " (directory):" : " (file):")); if(dir) { listRecursive(dir, depth); } else { FSLines *text = [file read]; for(NSString *line in text) { printIndent(depth); printf("\t%s\n", [line UTF8String]); } } } }
The function is passed a proxy to a directory to list, and an indent level. (The indent level increments with each recursive call and allows the code to print the name of each node at an indent level that corresponds to the depth of the tree at that node.) listRecursive
calls the list
operation on the directory and iterates over the returned sequence of nodes:
- The code does a
checkedCast
to narrow theNode
proxy to aDirectory
proxy, as well as anuncheckedCast
to narrow theNode
proxy to aFile
proxy. Exactly one of those casts will succeed, so there is no need to callcheckedCast
twice: if theNode
is-aDirectory
, the code uses theid<FSDirectoryPrx>
returned by thecheckedCast
; if thecheckedCast
fails, we know that theNode
is-aFile
and, therefore, anuncheckedCast
is sufficient to get anid<FSFilePrx>
.
In general, if you know that a down-cast to a specific type will succeed, it is preferable to use anuncheckedCast
instead of acheckedCast
because anuncheckedCast
does not incur any network traffic. - The code prints the name of the file or directory and then, depending on which cast succeeded, prints
"(directory)"
or"(file)"
following the name. - The code checks the type of the node:
- If it is a directory, the code recurses, incrementing the indent level.
- If it is a file, the code calls the
read
operation on the file to retrieve the file contents and then iterates over the returned sequence of lines, printing each line.
Assume that we have a small file system consisting of two files and a directory as follows:
A small file system.
The output produced by the client for this file system is:
Contents of root directory: README (file): This file system contains a collection of poetry. Coleridge (directory): Kubla_Khan (file): In Xanadu did Kubla Khan A stately pleasure-dome decree: Where Alph, the sacred river, ran Through caverns measureless to man Down to a sunless sea.
Note that, so far, our client (and server) are not very sophisticated:
- The transport protocol and address information are hard-wired into the code.
- The client makes more remote procedure calls than strictly necessary; with minor redesign of the Slice definitions, many of these calls can be avoided.
We will see how to address these shortcomings in our discussions of IceGrid and object life cycle.