NSFileHandle provides methods that let you read and write data from a file or communication channel asynchronously in the background. Chapter 2 discussed file access with NSFileHandle. This section describes NSFileHandle's asynchronous communications features and how they apply to networking.
You can obtain a socket file descriptor from an instance of NSSocketPort by sending a socket message to the socket port object. With the socket descriptor, you are able to initialize an instance of NSFileHandle with the method initWithFileDescriptor:. The initWithFileDescriptor:closeOnDealloc: method is an extension of this method that specifies whether or not the file descriptor will close when the file handle object is deallocated. By default, the file handle object does not close the file descriptor and ownership of that descriptor remains with the object that created the file handle. To determine an NSFileHandle instance's file descriptor, invoke the method fileDescriptor.
NSFileHandle provides the following three methods for performing asynchronous background communication:
This method is valid only for NSFileHandle instances initialized with a socket file descriptor (of type SOCK_STREAM), and causes the socket represented by the file handle to listen for new connections. This method returns immediately while a background thread accepts client connections over the socket. Observers are notified of new connections by registering for the notification NSFileHandleAcceptedConnectionNotification. The notification's userInfo dictionary contains a new socket file handle connected to the client that initiated the connection, which frees the listening socket to accept additional connections. You can obtain the socket from the userInfo dictionary through the key NSFileHandleNotificationFileHandleItem.
This method begins an asynchronous read operation on a socket in the background by invoking the method availableData. This method is generally invoked in the file handle object that represents the socket connected to the client that is obtained from the NSFileHandleAcceptedConnectionNotification userInfo dictionary. When data is read, the connected socket file handle posts an NSFileHandleReadCompletionNotification, whose userInfo dictionary contains the data that was read. You can obtain this NSData object by using the NSFileHandleNotificationDataItem key.
This method behaves similarly to readInBackgroundAndNotify, except the NSFileHandle method readToEndOfFile is invoked. When all data has finished being read, the file handle posts an NSFileHandleReadToEndOfFileCompletionNotification notification. You can obtain the read data from the userInfo dictionary by using the NSFileHandleNotificationDataItem key.
NSFileHandle declares three additional methods that include a ForModes: argument:
acceptConnectionInBackgroundAndNotifyForModes:
readInBackgroundAndNotifyForModes:
readToEndOfFileInBackgroundAndNotifyForModes:
The ForModes: argument specifies which run loop modes each method's notification may be posted in. This specification provides finer control over operation of the notification process.
Example 6-8 shows how to use NSFileHandle to implement a simple server infrastructure.
- (void)startServer { NSSocketPort *sockPort; sockPort = [[NSSocketPort alloc] initWithTCPPort:12345]; int socketFD = [sockPort socket]; NSFileHandle *listeningSocket; listeningSocket = [[NSFileHandle alloc] initWithFileDescriptor:socketFD]; NSNotificationCenter *nc; nc = [NSNotificationCenter defaultNotificationCenter]; [nc addObserver:self selector:@selector(spawnChildConnection:) name:NSFileHandleConnectionAcceptedNotification object:listeningSocket]; [listeningSocket acceptConnectionInBackgroundAndNotify]; } /* * This method is invoked in response to * NSFileHandleConnectionAcceptedNotification */ - (void)spawnChildConnection:(NSNotification *)note { NSFileHandle *connectedSocket = [[note userInfo] objectForKey:NSFileHandleNotificationFileHandleItem]; NSNotificationCenter *nc; nc = [NSNotificationCenter defaultNotificationCenter]; [nc addObserver:self selector:@selector(processClientData:) name:NSFileHandleReadCompletionNotification object:connectedSocket]; // Send a message to the client, acknowledging that the connection was accepted [connectedSocket writeData:ackData]; [connectedSocket readInBackgroundAndNotify]; } /* * This method is invoked in response to * NSFileHandleReadCompletionNotification */ - (void)processClientData:(NSNotification *)note { NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem]; // Do something here with your data // Tell file handle to continue waiting for data [[note object] readInBackgroundAndNotify]; }
Example 6-9 shows a client infrastructure using NSFileHandle.
- (void)startClient { NSSocketPort *sockPort; sockPort = [[NSSocketPort alloc] initRemoteWithTCPPort:12345 host:@"10.0.1.3"]; int sockFD = [sockPort socket]; NSFileHandle *clientSocket; clientSocket = [[NSFileHandle alloc] initWithFileDescriptor:sockFD]; NSNotificationCenter *nc; nc = [NSNotificationCenter defaultNotificationCenter]; [nc addObserver:self selector:@selector(processServerData:) name: NSFileHandleReadCompletionNotification object: clientSocket]; [clientSocket writeData:dataToWrite]; [clientSocket readInBackgroundAndNotify]; } /* * This method is invoked in response to * NSFileHandleReadCompletionNotification */ - (void) processServerData:(NSNotification *)note { NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem]; // Do something here with your data // Tell file handle to continue waiting for data [[note object] readInBackgroundAndNotify]; }