Polling Versus Run-Loop Scheduling

A potential problem with stream processing is blocking. A thread that is writing to or reading from a stream might have to wait indefinitely until there is (respectively) space on the stream to put bytes or bytes on the stream that can be read. In effect, the thread is at the mercy of the stream, and that can spell trouble for an application. Blocking can especially be a problem with socket streams because they are dependent on responses from a remote host.

With Cocoa streams you have two ways to handle stream events:

Run-loop scheduling is almost always preferable over polling, and that is why the code examples in “Reading From Input Streams” and “Writing To Output Streams” exclusively show the use of run loops. With polling, your program is locked in a tight loop, waiting for stream events that might or might not be imminent. With run-loop scheduling, your program can go off and do other things, knowing that it will be notified when there is a stream event to handle. Moreover, run loops save you from having to manage state and are more efficient than polling. Polling is also CPU-intensive; there are other things you can be doing with your processing time.

That said, there can be situations where polling is a viable option. For example, if you are porting legacy code, you might choose to use polling because it is better suited to the threading model in the legacy code. Listing 1 illustrates a method that writes data to an output stream using polling.

Listing 1  Writing to an output stream using polling

- (void)createNewFile {
    oStream = [[NSOutputStream alloc] initToMemory];
    [oStream open];
    uint8_t *readBytes = (uint8_t *)[data mutableBytes];
    uint8_t buf[1024];
    int len = 1024;
 
    while (1) {
        if (len == 0) break;
        if ( [oStream hasSpaceAvailable] ) {
        (void)strncpy(buf, readBytes, len);
        readBytes += len;
        if ([oStream write:(const uint8_t *)buf maxLength:len] == -1) {
            [self handleError:[oStream streamError]];
            break;
        }
        [bytesWritten setIntValue:[bytesWritten intValue]+len];
        len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 :
            [data length] - [bytesWritten intValue]);
        }
    }
    NSData *newData = [oStream propertyForKey:
        NSStreamDataWrittenToMemoryStreamKey];
    if (!newData) {
        NSLog(@"No data written to memory!");
    } else {
        [self processData:newData];
    }
    [oStream close];
    [oStream release];
    oStream = nil;
}

It should be pointed out that neither the polling nor run-loop scheduling approaches are airtight defenses against blocking. If the NSInputStream hasBytesAvailable method or the NSOutputStream hasSpaceAvailable method returns NO, it means in both cases that the stream definitely has no available bytes or space. However, if either of these methods returns YES, it can mean that there is available bytes or space or that the only way to find out is to attempt a read or a write operation (which could lead to a momentary block). The NSStreamEventHasBytesAvailable and NSStreamEventHasSpaceAvailable stream events have identical semantics.