Cocoa Streams

Streams provide an easy way for a program to exchange data with a variety of media in a device-independent way. A stream is a contiguous sequence of bits transmitted serially over a communications path. It is unidirectional and hence, from the perspective of a program, a stream can be an input (or read) stream or an output (or write) stream. Except for file-based streams, streams are non-seekable—once stream data has been provided or consumed, it cannot be retrieved again from the stream.

Cocoa includes three stream-related classes: NSStream, NSInputStream, and NSOutputStream. NSStream is an abstract class that defines the fundamental interface and properties for all stream objects. NSInputStream and NSOutputStream are subclasses of NSStream and implement default input-stream and output-stream behavior. You can create NSOutputStream instances for stream data located in memory or written to a file or C buffer; you can create NSInputStream instances for stream data read from an NSData object or a file. You can also have NSInputStream and NSOutputStream objects at the end points of a socket-based network connection and you can use stream objects without loading all of the stream data into memory at once. Figure 1 illustrates the types of input-stream and output-stream objects in terms of their sources or destinations.

Figure 1  Sources and destinations of stream objects
Sources and destinations of stream objects

Because they deal with such a basic computing abstraction (streams), NSStream and its subclasses are intended for lower-level programming tasks. If there is a higher-level Cocoa API that is more suited for a particular task (for example, NSURL or NSFileHandle) use it instead.

Stream objects have properties associated with them. Most properties have to do with network security and configuration, namely secure-socket (SSL) levels and SOCKS proxy information. Two important additional properties are NSStreamDataWrittenToMemoryStreamKey, which permits retrieval of data written to memory for an output stream, and NSStreamFileCurrentOffsetKey, which allows you to manipulate the current read or write position in file-based streams.

A stream object also has a delegate associated with it. If a delegate is not explicitly set, the stream object itself becomes the delegate (a useful convention for custom subclasses). A stream object invokes the sole delegation method stream:handleEvent: for each stream-related event it handles. Of particular importance are the events that indicate when bytes are available to read from an input stream and when an output stream signals that it’s ready to accept bytes. For these two events, the delegate sends the stream the appropriate message—read:maxLength: or write:maxlength:, depending on type of stream—to get the bytes from the stream or to put bytes on the stream.

NSStream is built on the CFStream layer of Core Foundation. This close relationship means that the concrete subclasses of NSStream, NSOutputStream and NSInputStream, are toll-free bridged with their Core Foundation counterparts CFWriteStream and CFReadStream. Although there are strong similarities between the Cocoa and Core Foundation stream APIs, their implementations are not exactly coincident. The Cocoa stream classes use the delegation model for asynchronous behavior (assuming run-loop scheduling) while Core Foundation uses client callbacks. The Core Foundation stream types sets the client (termed a context in Core Foundation) differently than the NSStream sets the delegate; calls to set the delegate should not be mixed with calls to set the context. Otherwise you can freely intermix calls from the two APIs in your code.

Despite their strong similarities, NSStream does give you a major advantage over CFStream. Because of its Objective-C underpinnings, it is extensible. You can subclass NSStream, NSInputStream, or NSOutputStream to customize stream attributes and behavior. For example, you could create an input stream that maintains statistics on the bytes it reads; or you could make a NSStream subclass whose instances can seek through their stream, putting back bytes that have been read. NSStream has its own set of required overrides, as do NSInputStream and NSOutputStream. See the reference documentation for NSStream, NSInputStream, and NSOutputStream for details on subclassing these classes.