Writing To Output Streams
Using an NSOutputStream
instance to write to an output stream requires several steps:
Create and initialize an instance of
NSOutputStream
with a repository for the written data. Also set a delegate.Schedule the stream object on a run loop and open the stream.
Handle the events that the stream object reports to its delegate.
If the stream object has written data to memory, obtain the data by requesting the
NSStreamDataWrittenToMemoryStreamKey
property.When there is no more data to write, dispose of the stream object.
The following discussion goes into each of these steps in more detail.
Preparing the Stream Object
To begin using an NSOutputStream
object you must specify a destination for the data written to the stream. The destination for an output-stream object can be a file, a C buffer, application memory, or a network socket.
The initializers and factory methods for NSOutputStream
allow you to create and initialize the instance with a file, a buffer, or memory. Listing 1 shows the creation of an NSOutputStream
instance that will write data to application memory.
Listing 1 Creating and initializing an NSOutputStream object for memory
- (void)createOutputStream { |
NSLog(@"Creating and opening NSOutputStream..."); |
// oStream is an instance variable |
oStream = [[NSOutputStream alloc] initToMemory]; |
[oStream setDelegate:self]; |
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] |
forMode:NSDefaultRunLoopMode]; |
[oStream open]; |
} |
As the code in Listing 1 shows, after you create the object you should set the delegate (more often than not to self
). The delegate receives stream:handleEvent:
messages from the NSOutputStream
object when that object has stream-related events to report, such as when the stream has space for bytes.
Before you open the stream to begin the streaming of data, send a scheduleInRunLoop:forMode:
message to the stream object to schedule it to receive stream events on a run loop. By doing this, you are helping the delegate to avoid blocking when the stream is unable to accept more bytes. If streaming is taking place on another thread, be sure to schedule the stream object on that thread’s run loop. You should never attempt to access a scheduled stream from a thread different than the one owning the stream’s run loop. Finally, send the NSOutputStream
instance an open
message to start the streaming of data to the output container.
Handling Stream Events
After a stream object is sent open
, you can find out about its status, whether it has space for writing data, and the nature of any error with the following messages:
streamStatus
hasSpaceAvailable
streamError
The returned status is an NSStreamStatus
constant indicating that the stream is opening, writing, at the end of the stream, and so on. The returned error is an NSError
object encapsulating information about any error that took place. (See the reference documentation for NSStream for descriptions of NSStreamStatus
and other stream types.)
More importantly, once the stream object has been opened, it keeps sending stream:handleEvent:
messages to its delegate (as long as the delegate continues to put bytes on the stream) until it encounters the end of the stream. These messages include a parameter with an NSStreamEvent
constant that indicates the type of event. For NSOutputStream
objects, the most common types of events are NSStreamEventOpenCompleted
, NSStreamEventHasSpaceAvailable
, and NSStreamEventEndEncountered
. The delegate is typically most interested in NSStreamEventHasSpaceAvailable
events. Listing 2 illustrates one approach you could take to handle this type of event.
Listing 2 Handling a space-available event
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode |
{ |
switch(eventCode) { |
case NSStreamEventHasSpaceAvailable: |
{ |
uint8_t *readBytes = (uint8_t *)[_data mutableBytes]; |
readBytes += byteIndex; // instance variable to move pointer |
int data_len = [_data length]; |
unsigned int len = ((data_len - byteIndex >= 1024) ? |
1024 : (data_len-byteIndex)); |
uint8_t buf[len]; |
(void)memcpy(buf, readBytes, len); |
len = [stream write:(const uint8_t *)buf maxLength:len]; |
byteIndex += len; |
break; |
} |
// continued ... |
} |
} |
In this implementation of stream:handleEvent:
the delegate uses a switch statement to identify the passed-in NSStreamEvent
constant. If the constant is NSStreamEventHasSpaceAvailable
, the delegate gets the bytes held by a NSMutableData
object (_data
) and advances the pointer for the current write operation. It next determines the byte capacity of the impending write operation (1024 or the remaining bytes to write), declares a buffer of that size, and copies that amount of data to the buffer. Next the delegate invokes the output-stream object’s write:maxLength:
method to put the buffer’s contents onto the output stream. Finally it advances the index used to advance the readBytes
pointer for the next operation.
If the delegate receives an NSStreamEventHasSpaceAvailable
event and does not write anything to the stream, it does not receive further space-available events from the run loop until the NSOutputStream
object receives more bytes. When this happens, the run loop is restarted for space-available events. If this scenario is likely in your implementation, you can have the delegate set a flag when it doesn’t write to the stream upon receiving an NSStreamEventHasSpaceAvailable
event. Later, when your program has more bytes to write, it can check this flag and, if set, write to the output-stream instance directly.
There is no firm guideline on how many bytes to write at one time. Although it may be possible to write all the data to the stream in one event, this depends on external factors, such as the behavior of the kernel and device and socket characteristics. The best approach is to use some reasonable buffer size, such as 512 bytes, one kilobyte (as in the example above), or a page size (four kilobytes).
When the NSOutputStream
object experiences errors writing to the stream, it stops streaming and notifies its delegate with a NSStreamEventErrorOccurred
. The delegate should handle the error in its stream:handleEvent:
method as described in Handling Stream Errors.
Disposing of the Stream Object
When an NSOutputStream
object concludes writing data to an output stream, it sends the delegate a NSStreamEventEndEncountered
event in a stream:handleEvent:
message. At this point the delegate should dispose of the stream object by doing the mirror-opposite of what it did to prepare the object. In other words, it should first close the stream object, remove it from the run loop, and finally release it. Furthermore, if the destination for the NSOutputStream
object is application memory (that is, you created the instance using initToMemory
or the factory method outputStreamToMemory
), you might now want to retrieve the data held in memory. Listing 3 illustrates how you might do all of these things.
Listing 3 Closing and releasing the NSInputStream object
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode |
{ |
switch(eventCode) { |
case NSStreamEventEndEncountered: |
{ |
NSData *newData = [oStream propertyForKey: |
NSStreamDataWrittenToMemoryStreamKey]; |
if (!newData) { |
NSLog(@"No data written to memory!"); |
} else { |
[self processData:newData]; |
} |
[stream close]; |
[stream removeFromRunLoop:[NSRunLoop currentRunLoop] |
forMode:NSDefaultRunLoopMode]; |
[stream release]; |
oStream = nil; // oStream is instance variable |
break; |
} |
// continued ... |
} |
} |
You get the stream data written to memory by sending the NSOutputStream
object a propertyForKey:
message, specifying a key of NSStreamDataWrittenToMemoryStreamKey
The stream object returns the data in an NSData
object.
Copyright © 2004, 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-12-16